# Port Documentation > Port is the platform for building your AI software factory: one governed layer where humans and AI agents build, govern, and operate the agentic SDLC. API base URLs: EU https://api.port.io, US https://api.us.port.io. - [Port Documentation](/index.md) ## Getting started ### overview Set up your Port account, connect your first integration, and build a working software catalog in a few guided onboarding steps. - [Getting started](/getting-started/overview.md): Set up your Port account, connect your first integration, and build a working software catalog in a few guided onboarding steps. ### agentic-sdlc-pillars An overview of the six pillars that make up a software factory in Port - context lake, workflows, Port AI, agent management, interface builder, and governance. - [The pillars of your software factory](/getting-started/agentic-sdlc-pillars.md): An overview of the six pillars that make up a software factory in Port - context lake, workflows, Port AI, agent management, interface builder, and governance. ### build-more-workflows Explore more workflow examples and keep building on top of your context lake. - [Build more workflows](/getting-started/build-more-workflows.md): Explore more workflow examples and keep building on top of your context lake. ### build-your-first-workflow Build your first AI-powered workflow in minutes, using any use-case Port AI suggests for your context lake. - [Build your first workflow](/getting-started/build-your-first-workflow.md): Build your first AI-powered workflow in minutes, using any use-case Port AI suggests for your context lake. ### connect-your-git Connect your Git provider so Port can start populating your context lake with repositories and pull requests. - [Set up your Git provider](/getting-started/connect-your-git.md): Connect your Git provider so Port can start populating your context lake with repositories and pull requests. ### craft-an-initial-prompt Choose a use-case, craft an initial prompt, and let Port AI build the initial structure of your platform. - [Craft a prompt](/getting-started/craft-an-initial-prompt.md): Choose a use-case, craft an initial prompt, and let Port AI build the initial structure of your platform. ### docs-ai-assistant Use Port's AI assistant to ask documentation questions, generate resource definitions, debug JSON/YAML, and get help with blueprints and actions. - [Docs AI assistant](/getting-started/docs-ai-assistant.md): Use Port's AI assistant to ask documentation questions, generate resource definitions, debug JSON/YAML, and get help with blueprints and actions. ### invite-users Invite colleagues to your Port platform to collaborate on workflows and catalog management. - [Invite your team](/getting-started/invite-users.md): Invite colleagues to your Port platform to collaborate on workflows and catalog management. ### set-up-your-context-lake Connect additional tools and populate your context lake with services, users, and ownership data. - [Set up your context lake](/getting-started/set-up-your-context-lake.md): Connect additional tools and populate your context lake with services, users, and ownership data. ### view-dashboards Explore the dashboard Port auto-created for your use-case, now populated with real data from your tools. - [View your dashboards](/getting-started/view-dashboards.md): Explore the dashboard Port auto-created for your use-case, now populated with real data from your tools. ## context-lake ### business-context #### organization-hierarchy Model your organization hierarchy with team blueprints. Create views showing groups, teams, and services at each tier level. - [Visualize organization hierarchy](/context-lake/business-context/organization-hierarchy.md): Model your organization hierarchy with team blueprints. Create views showing groups, teams, and services at each tier level. #### overview Add business context to your Port catalog so agents and workflows can prioritize by cost, criticality, ownership, SLAs, and risk. - [Business context](/context-lake/business-context/overview.md): Add business context to your Port catalog so agents and workflows can prioritize by cost, criticality, ownership, SLAs, and risk. #### ownership Assign users and teams as owners of catalog entities. Sync ownership from integrations, configure the ownership property and $team meta-property, and visualize who owns what in your portal. - [Manage ownership in Port](/context-lake/business-context/ownership.md): Assign users and teams as owners of catalog entities. Sync ownership from integrations, configure the ownership property and $team meta-property, and visualize who owns what in your portal. ### consuming-the-lake #### govern-data-access ##### examples Examples of catalog RBAC configurations including making entities immutable, property-level permissions, and team ownership-based access control. - [Examples](/context-lake/consuming-the-lake/govern-data-access/examples.md): Examples of catalog RBAC configurations including making entities immutable, property-level permissions, and team ownership-based access control. ##### overview Set Port catalog RBAC so you control who reads, creates, updates, or deletes entities and properties by role, team, or policy. - [Govern data access](/context-lake/consuming-the-lake/govern-data-access/overview.md): Set Port catalog RBAC so you control who reads, creates, updates, or deletes entities and properties by role, team, or policy. #### other-consumption-methods ##### overview Use the Port n8n node and related methods so external automation can query your Context Lake without direct access to every tool. - [Other consumption methods](/context-lake/consuming-the-lake/other-consumption-methods/overview.md): Use the Port n8n node and related methods so external automation can query your Context Lake without direct access to every tool. ##### port-n8n-node Install and configure Port's custom n8n node to integrate Port AI agents and API into your n8n automation workflows. - [Port n8n node](/context-lake/consuming-the-lake/other-consumption-methods/port-n8n-node.md): Install and configure Port's custom n8n node to integrate Port AI agents and API into your n8n automation workflows. #### overview See how you consume Port's Context Lake: query entities, use Port MCP, apply catalog RBAC, and trigger external automation. - [Consumption methods](/context-lake/consuming-the-lake/overview.md): See how you consume Port's Context Lake: query entities, use Port MCP, apply catalog RBAC, and trigger external automation. #### port-mcp-server The Port MCP server does not use Port AI capabilities. You can install and use it without enabling Port AI features or accepting the Port AI Terms. - [Port MCP server](/context-lake/consuming-the-lake/port-mcp-server.md): The Port MCP server does not use Port AI capabilities. You can install and use it without enabling Port AI features or accepting the Port AI Terms. #### search-and-query ##### advanced Advanced search options including attach_title_to_relation and exclude_calculated_properties query parameters for customizing search results. - [Advanced](/context-lake/consuming-the-lake/search-and-query/advanced.md): Advanced search options including attach_title_to_relation and exclude_calculated_properties query parameters for customizing search results. ##### examples Search and query examples for catalog pages, dashboard widgets, automations, and dynamic action permissions with practical JSON rules. - [Examples](/context-lake/consuming-the-lake/search-and-query/examples.md): Search and query examples for catalog pages, dashboard widgets, automations, and dynamic action permissions with practical JSON rules. ##### operators - [Comparison operators](/context-lake/consuming-the-lake/search-and-query/operators/comparison-operators.md): Comparison operators for search rules including equals, contains, greater/less than, between, isEmpty, and property existence checks. - [Relation operators](/context-lake/consuming-the-lake/search-and-query/operators/relation-operators.md): Relation operators for searching entities by relationships including relatedTo, containsAny, and direction/depth controls for traversing relations. ##### overview Apply Port's search and query syntax to filter entities in pages, dashboards, scorecards, RBAC, actions, and API search endpoints. - [API access & search](/context-lake/consuming-the-lake/search-and-query/overview.md): Apply Port's search and query syntax to filter entities in pages, dashboards, scorecards, RBAC, actions, and API search endpoints. ##### structure-and-syntax Search query structure with combinators (and/or), rules, property operators, and meta-properties like $blueprint and $identifier. - [Structure & syntax](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax.md): Search query structure with combinators (and/or), rules, property operators, and meta-properties like $blueprint and $identifier. ### data-model #### configure-data-model Learn how Port's data model uses blueprints, properties, and relations so you can define and manage your catalog schema. - [Data model](/context-lake/data-model/configure-data-model.md): Learn how Port's data model uses blueprints, properties, and relations so you can define and manage your catalog schema. #### define-your-ontology Learn how blueprint descriptions, relation titles, property types, and enum values turn a schema into a semantic knowledge graph that AI agents can reason over and act on. - [Define your ontology](/context-lake/data-model/define-your-ontology.md): Learn how blueprint descriptions, relation titles, property types, and enum values turn a schema into a semantic knowledge graph that AI agents can reason over and act on. #### iac ##### pulumi-managed-blueprint Comprehensive blueprint with properties, relations and mirror properties - [Pulumi-Managed Blueprint Example](/context-lake/data-model/iac/pulumi-managed-blueprint.md): Comprehensive blueprint with properties, relations and mirror properties ##### terraform-managed-blueprint Comprehensive blueprint with properties, relations and mirror properties - [Terraform-Managed Blueprint Example](/context-lake/data-model/iac/terraform-managed-blueprint.md): Comprehensive blueprint with properties, relations and mirror properties #### setup-blueprint ##### default-blueprints Reference for default Port blueprints including service, workload, environment, user, and team. Describes each blueprint's purpose and properties. - [Default blueprints](/context-lake/data-model/setup-blueprint/default-blueprints.md): Reference for default Port blueprints including service, workload, environment, user, and team. Describes each blueprint's purpose and properties. ##### overview Create Port blueprints to model your assets, then add properties and relations using Port AI, the UI, API, Terraform, or Pulumi. - [Set up blueprints](/context-lake/data-model/setup-blueprint/overview.md): Create Port blueprints to model your assets, then add properties and relations using Port AI, the UI, API, Terraform, or Pulumi. ##### properties - [Aggregation property](/context-lake/data-model/setup-blueprint/properties/aggregation-property.md): Learn how to use aggregation properties to calculate metrics based on relations in your catalog, such as counting entities or averaging values. - [Array](/context-lake/data-model/setup-blueprint/properties/array.md): Array is a data type used to save lists of data - [Boolean](/context-lake/data-model/setup-blueprint/properties/boolean.md): Boolean is a primitive data type that has one of two possible values - true and false - [Calculation property](/context-lake/data-model/setup-blueprint/properties/calculation-property.md): Calculation property allows you to construct new data from existing properties of an entity - [Examples](/context-lake/data-model/setup-blueprint/properties/calculation-property/examples.md): Examples of calculation properties including math operations, string concatenation, conditional logic, and date formatting in Port blueprints. - [Datetime](/context-lake/data-model/setup-blueprint/properties/datetime.md): Datetime is a data type used to reference a date and time - [Email](/context-lake/data-model/setup-blueprint/properties/email.md): Email is a data type used to save Email addresses - [Embedded URL](/context-lake/data-model/setup-blueprint/properties/embedded-url.md): Learn how to use embedded URL properties to display external webpages like dashboards, diagrams, and architecture views within entity pages. - [Authentication](/context-lake/data-model/setup-blueprint/properties/embedded-url/authentication.md): Learn how to configure SSO authentication for embedded URL properties to display protected webpages within Port entity pages. - [Enum](/context-lake/data-model/setup-blueprint/properties/enum.md): Enum is a data type used to define a named set of constant values. - [Labeled URL](/context-lake/data-model/setup-blueprint/properties/labeled-url-object.md): Labeled URL is an object type used to store URLs with custom display labels - [Markdown](/context-lake/data-model/setup-blueprint/properties/markdown.md): Learn how to use the Markdown property type to display formatted Markdown content within entity pages in Port. - [Meta-Properties](/context-lake/data-model/setup-blueprint/properties/meta-properties.md): Reference for meta-properties that exist on every entity by default, including identifier, title, team, timestamps, and created/updated by fields. - [Mirror property](/context-lake/data-model/setup-blueprint/properties/mirror-property.md): Mirror Property allows you to map data from related entities to your entity - [Number](/context-lake/data-model/setup-blueprint/properties/number.md): Number is a primitive data type used to save numeric data - [JSON](/context-lake/data-model/setup-blueprint/properties/object.md): JSON is a data type used to save structured data as key-value pairs. - [Properties](/context-lake/data-model/setup-blueprint/properties/overview.md): Configure Port blueprint properties from the Data model page or catalog so you can create, edit, and hide the fields your entities store. - [Owning teams](/context-lake/data-model/setup-blueprint/properties/owning-teams.md): Owning Teams defines which team owns a blueprint's entities. It can be added once per blueprint, creates a relation to the Team blueprint, and sets the blueprint's ownership. - [Protobuf](/context-lake/data-model/setup-blueprint/properties/proto.md): Protobuf is a data type used to save Protocol Buffers definitions. - [String](/context-lake/data-model/setup-blueprint/properties/string.md): Configure string properties on Port blueprints, including enum values, formats, and validation rules. String is a primitive data type used to save text data. - [Swagger UI](/context-lake/data-model/setup-blueprint/properties/swagger.md): Learn how to use the Swagger UI property to display OpenAPI and AsyncAPI specification files within entity pages in Port. - [Team](/context-lake/data-model/setup-blueprint/properties/team.md): Team is a data type used to reference teams that exist in Port - [Timer](/context-lake/data-model/setup-blueprint/properties/timer.md): Timer is a data type used to define an expiration date/lifespan of a specific entity - [URL](/context-lake/data-model/setup-blueprint/properties/url.md): URL is a data type used to save links to websites - [User](/context-lake/data-model/setup-blueprint/properties/user.md): User is a data type used to reference users that exist in Port - [YAML](/context-lake/data-model/setup-blueprint/properties/yaml.md): YAML is a data type used to save object definitions in YAML. ##### relate-blueprints Learn how to create relations between blueprints to define logical connections between assets in your software catalog. - [Relate Blueprints](/context-lake/data-model/setup-blueprint/relate-blueprints.md): Learn how to create relations between blueprints to define logical connections between assets in your software catalog. ### extend-the-context-lake You can extend the Context Lake beyond native integrations by connecting external MCP servers and publishing skills. - [Extend the context lake](/context-lake/extend-the-context-lake.md): You can extend the Context Lake beyond native integrations by connecting external MCP servers and publishing skills. ### ingestion #### catalog-auto-discovery Learn how to use AI-powered auto discovery to identify and create missing entities in your catalog based on related data from multiple sources. - [Auto discovery](/context-lake/ingestion/catalog-auto-discovery.md): Learn how to use AI-powered auto discovery to identify and create missing entities in your catalog based on related data from multiple sources. #### configure-mapping ##### common-jq-use-cases Common JQ patterns for integration mappings, self-service action backends, and filters. - [Common jq use-cases](/context-lake/ingestion/configure-mapping/common-jq-use-cases.md): Common JQ patterns for integration mappings, self-service action backends, and filters. ##### configuration-methods Configure an integration's mapping using either the visual form editor or the code-based YAML editor. - [Configuration methods](/context-lake/ingestion/configure-mapping/configuration-methods.md): Configure an integration's mapping using either the visual form editor or the code-based YAML editor. ##### entity-cleanup When you remove a resource type from your integration mapping or decommission an integration, the associated entities in Port are not automatically deleted. This documentation explains how to properly clean up stale entities to keep your software catalog accurate and up to date. - [Clean up stale entities](/context-lake/ingestion/configure-mapping/entity-cleanup.md): When you remove a resource type from your integration mapping or decommission an integration, the associated entities in Port are not automatically deleted. This documentation explains how to properly clean up stale entities to keep your software catalog accurate and up to date. ##### overview Edit Port integration mapping with JQ to select source data, map properties and relations, and tune sync in YAML or forms. - [Configure mapping](/context-lake/ingestion/configure-mapping/overview.md): Edit Port integration mapping with JQ to select source data, map properties and relations, and tune sync in YAML or forms. #### ingest-data-into-port ##### api - [Advanced](/context-lake/ingestion/ingest-data-into-port/api/advanced.md): Advanced API query parameters including delete_dependents for cascading deletes and create_missing_related_entities for automatic entity creation. - [CI/CD](/context-lake/ingestion/ingest-data-into-port/api/ci-cd.md): Overview of CI/CD integrations for keeping your software catalog up to date with build versions, job statuses, and other CI/CD data. - [Azure Pipelines](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/azure-pipelines.md): Learn how to create, update, and query Port entities directly from Azure Pipelines using Port's API. - [Examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/azure-pipelines/examples.md): Examples of creating and updating Port entities from Azure Pipelines, including CI job tracking and Docker image reporting. - [CircleCI workflows](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/circleci-workflow.md): Learn how to create, update, and query Port entities directly from CircleCI workflows using Port's API. - [Examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/circleci-workflow/examples.md): Examples of creating and updating Port entities from CircleCI workflows, including CI job tracking and Docker image reporting. - [Codefresh Workflow Template](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/codefresh-workflow-template.md): Learn how to use Port's Codefresh workflow template to create, update, and query entities directly from Codefresh pipelines. - [Examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/codefresh-workflow-template/examples.md): Basic create/update example - [GitHub Workflow](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/github-workflow.md): Learn how to use Port's GitHub action to create, update, and query entities directly from GitHub workflows. - [Examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/github-workflow/examples.md): Examples of using Port's GitHub action to create entities, import dependencies, and report CI job status from GitHub workflows. - [Gitlab CI Pipelines](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/gitlab-pipelines.md): Learn how to create, update, and query Port entities directly from GitLab CI pipeline jobs using Port's API. - [Examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/gitlab-pipelines/examples.md): Examples of creating and updating Port entities from GitLab CI pipelines, including CI job tracking and Docker image reporting. - [Jenkins Deployment](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/jenkins-deployment.md): Learn how to create, update, and query Port entities directly from Jenkins pipelines using Port's API. - [Examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/jenkins-deployment/examples.md): Examples of creating and updating Port entities from Jenkins pipelines, including CI job tracking and Docker image reporting. - [Ingest via API](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview.md): Authenticate with Port's REST API to ingest entities and update your catalog from scripts, CI pipelines, or custom systems. ##### custom-integration - [Implement an API client](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/api-client.md): Build an async third-party API client for a custom-coded Ocean integration using Ocean's HTTP client. - [Define configuration files](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/configuration-files.md): Configure .port/spec.yaml, default blueprints, and default mapping for a custom-coded Ocean integration. - [Configuration validation](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/configuration-validation.md): How Ocean validates integration configuration from .port/spec.yaml at startup. - [Ocean and event contexts](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/contexts.md): Use the ocean and event context objects when implementing a custom-coded Ocean integration. - [Deploy a custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/deploy.md): Build and deploy a custom-coded Ocean integration image with Docker or Helm. - [Development guidelines](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/guidelines.md): Practices for custom-coded Ocean integrations - async HTTP, raw data shape, logging, and performance. - [Implement incremental sync](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/incremental-sync.md): Add @ocean.on_incremental_resync handlers that fetch only changes since the last incremental cursor. - [Kinds and selectors](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/kinds-and-selectors.md): Add custom mapping selector fields for Ocean kinds using integration.py. - [Implement live events](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/live-events.md): Implement AbstractWebhookProcessor classes that return raw updated or deleted objects for live events. - [Write your own Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/overview.md): Build flow for a custom-coded Ocean integration, from configuration files through resyncs, incremental sync, live events, testing, and deploy. - [Handle resyncs](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/resyncs.md): Implement @ocean.on_resync handlers that yield batches of raw third-party data. - [Test and run locally](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/testing.md): Map spec.yaml to environment variables, run a custom-coded Ocean integration locally, and trigger a resync. - [Getting started](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/getting-started.md): Install the Ocean CLI, scaffold a custom-coded Ocean integration, and run it locally. - [Custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/overview.md): Build a custom-coded Python Ocean integration when the no-code option is not enough, and let Port apply your mapping to the raw data you extract. - [Configuration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/configuration.md): Prerequisites, resource mapping, and advanced configuration for the no-code Ocean custom integration - [Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port.md): Install the no-code Ocean custom integration hosted by Port - [Build your integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration.md): Interactive guide to configure and install your integration - [Custom authentication](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/custom-authentication.md): Configure OAuth2, JWT, and other dynamic token-based authentication flows - [Setup](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup.md): Install the no-code Ocean custom integration in your own infrastructure - [No-code Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview.md): Connect any REST API to Port with the no-code Ocean custom integration so you can map endpoints to blueprints and ingest with config only. - [Create a custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/overview.md): Build a custom Port integration when no native option exists. Define blueprints, then ingest via Ocean, webhooks, the API, or IaC. ##### native-integrations - [Available integrations](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations.md): Browse all available Port native integrations. Each card links to the integration's documentation. - [Claude AI metrics](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/claude.md): Ingest Claude AI (Enterprise) and Claude Platform usage, analytics, and skill adoption into Port to track AI tool adoption across your organization. - [Cursor metrics](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/cursor.md): Learn how to ingest Cursor team, model, and skill usage into Port to track AI adoption and cost patterns across your engineering organization using the no-code Ocean custom integration framework. - [GitHub Copilot metrics](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/github-copilot.md): Ingest GitHub Copilot usage and metrics into Port to track AI coding tool adoption across your engineering organization. - [OpenAI metrics](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/openai.md): Learn how to ingest OpenAI usage metrics, costs, and model information into Port using the no-code Ocean custom integration framework. - [APM & alerting](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting.md): Overview of APM and alerting integrations for syncing monitoring data from tools like Datadog, Prometheus, and Azure Monitor to Port. - [Azure Monitor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/azure-monitor.md): Learn how to ingest Azure Monitor alerts, activity logs, and metrics into Port using the no-code Ocean custom integration framework. - [Datadog](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog.md): Sync Datadog monitors, services, SLOs, and host data into Port's software catalog, and use it across scorecards, dashboards, and automations. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/examples.md): Mapping examples for Datadog resources including monitors, services, SLOs, and dashboards using the Datadog integration. - [Dynatrace](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/dynatrace.md): Sync Dynatrace monitoring data, problems, and services into Port's software catalog. - [New Relic](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/newrelic.md): Sync New Relic monitoring data, alerts, and services into Port's software catalog. - [Prometheus](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/prometheus.md): Learn how to ingest Prometheus alerts, rules, and scrape targets into Port using the no-code Ocean custom integration framework. - [Sentry](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/sentry.md): Sync Sentry projects, issues, and error tracking data into Port's software catalog. - [Argo CD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd.md): Sync Argo CD applications, deployments, and clusters into Port's software catalog. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd/example.md): Mapping examples for ArgoCD resources including clusters, projects, applications, and deployment history. - [n8n](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/n8n.md): Learn how to ingest n8n users, projects, and workflows into Port using the no-code Ocean custom integration framework. - [Zapier](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/zapier.md): Learn how to ingest Zapier apps and zaps into Port using the no-code Ocean custom integration framework. - [Jenkins](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/jenkins.md): Sync Jenkins jobs, builds, and pipelines into Port's software catalog. - [Octopus Deploy Integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy.md): Sync Octopus Deploy projects, releases, and deployments into Port's software catalog. - [Ingest additional resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/mapping-extra-resources.md): Learn how to map additional Octopus Deploy resources like runbooks, tasks, teams, environments, and workers into Port. - [Cloud cost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost.md): Overview of cloud cost integrations for syncing cost and usage data from tools like Kubecost and OpenCost to Port. - [Kubecost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost.md): Sync Kubecost cost allocation and Kubernetes spend data into Port's software catalog. - [Advanced configuration for Kubecost objects](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/advanced.md): Advanced configuration options for Kubecost integration including allocation queries, filters, and cloud cost aggregation parameters. - [OpenCost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/opencost.md): Sync OpenCost Kubernetes cost and resource spend data into Port's software catalog. - [Cloud providers](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers.md): Overview of cloud provider integrations for syncing AWS, Azure, and GCP resources to your Port software catalog. - [Installation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/installation.md): Install the Port AWS integration using Port-hosted or self-hosted deployment. Covers single account and multi-account setups via ECS, EC2, EKS (IRSA), or one-off Docker runs. - [Live events](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/live-events.md): How AWS live events work, which resource kinds are supported, and troubleshooting. - [AWS](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/Overview.md): Learn how to sync AWS resources like EC2, S3, Lambda, EKS, and more to Port using the AWS integration with flexible deployment options. - [AWS resource and property reference](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference.md): Reference guide for AWS resource types, actions, and properties supported by the AWS integration including EC2, S3, ECS, Lambda, and more. - [Amazon DynamoDB](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-dynamodb.md): Reference for Amazon DynamoDB resources including tables with supported actions, permissions, and mapping configurations. - [Amazon Elastic Compute Cloud (EC2)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elastic-compute-cloud.md): Reference for Amazon EC2 resources including instances and volumes with supported actions, permissions, and mapping configurations. - [Amazon Elastic Container Registry (ECR)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elastic-container-registry.md): Reference for Amazon ECR repository resources including supported actions, permissions, and mapping configurations for the AWS integration. - [Amazon Elastic Container Service (ECS)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elastic-container-service.md): Reference for Amazon ECS resources including clusters, services, and task definitions with supported actions and mapping configurations. - [Amazon Elastic Kubernetes Service (EKS)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elastic-kubernetes-service.md): Reference for Amazon EKS cluster resources including supported actions, permissions, and mapping configurations for the AWS integration. - [Amazon ElastiCache](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elasticache.md): Reference for Amazon ElastiCache resources including supported actions, permissions, and mapping configurations for the AWS integration. - [Amazon Managed Streaming for Apache Kafka (MSK)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-managed-streaming-for-apache-kafka.md): Reference for Amazon MSK cluster resources including supported actions, permissions, and mapping configurations for the AWS integration. - [Amazon MemoryDB](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-memorydb.md): Reference for Amazon MemoryDB user resources including supported actions, permissions, and mapping configurations for the AWS integration. - [Amazon Relational Database Service (RDS)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-relational-database-service.md): Reference for Amazon RDS DB instance resources including supported actions, permissions, and mapping configurations for the AWS integration. - [Amazon Simple Email Service (SES)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-simple-email-service.md): Reference for Amazon SES resources including supported actions, permissions, and mapping configurations for the AWS integration. - [Amazon Simple Queue Service (SQS)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-simple-queue-service.md): Reference for Amazon SQS resources including supported actions, permissions, and mapping configurations for the AWS integration. - [Amazon Simple Storage Service (S3)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-simple-storage-service.md): Reference for Amazon S3 bucket resources including supported actions, permissions, and mapping configurations for the AWS integration. - [AWS Account](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-account.md): Reference for AWS Account information resources for resyncing account data without AWS Organizations permissions. - [AWS CodeBuild](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-codebuild.md): Reference for AWS CodeBuild resources including projects and build runs with supported actions, permissions, and mapping configurations. - [AWS CodeDeploy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-codedeploy.md): Reference for AWS CodeDeploy resources including applications, deployment groups, deployments, and deployment targets with supported actions and mapping configurations. - [AWS CodePipeline](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-codepipeline.md): Reference for AWS CodePipeline resources including pipelines, stages, actions, and executions with supported actions, permissions, and mapping configurations. - [AWS Lambda](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-lambda.md): Reference for AWS Lambda resources including supported actions, permissions, and mapping configurations for the AWS integration. - [AWS Organizations](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-organizations.md): Reference for AWS Organizations account resources including supported actions, permissions, and mapping configurations for the AWS integration. - [AWS Legacy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws.md): Sync AWS resources into Port's software catalog using the AWS Legacy integration. - [AWS exporter](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter.md): Deprecated AWS exporter documentation - learn about the legacy serverless application for syncing AWS resources to Port. - [Event-based updates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/event-based-updates.md): Learn how to configure real-time event-based updates for the deprecated AWS exporter using EventBridge and CloudTrail. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/examples.md): Examples of mapping AWS resources like ECS clusters, S3 buckets, Lambda functions, and more using the deprecated AWS exporter. - [Connect ECR repository to a service](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/examples/connect-ecr-repo-to-service-using-tags.md): Learn how to connect AWS ECR repositories to existing services in Port using resource tags for the deprecated AWS exporter. - [Installation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/installation.md): Installation guide for the deprecated AWS exporter using Terraform or manual SAM deployment methods. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/examples.md): Base mapping examples for AWS resources including accounts and generic cloud resources using the AWS Ocean integration. - [Mapping extra resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/examples/mapping_extra_resources.md): Learn how to map additional AWS resources to Port including ECS, Lambda, SQS, DynamoDB, and other Cloud Control API supported resources. - [Live events setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/live-events.md): Learn how to set up real-time event processing for AWS resources using EventBridge and CloudTrail to keep your Port catalog updated. - [Multi account support](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/multi_account.md): Learn how to configure the AWS Ocean integration to ingest resources from multiple AWS accounts using cross-account IAM roles. - [Azure](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure.md): Sync Azure cloud resources into Port's software catalog. - [Azure Resource Graph (Beta)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/azure-resource-graph.md): Learn how to sync Azure resources to Port using the Azure Resource Graph integration for high-volume, multi-subscription data ingestion. - [Resource Templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates.md): Base mapping templates for Azure resources including subscriptions, resource groups, and generic cloud resources using the Azure integration. - [Advanced Templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/advanced.md): Advanced mapping templates for Azure resources including storage accounts, AKS clusters, virtual machines, and Service Bus resources. - [Mapping Extra Resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/mapping_extra_resources.md): Learn how to map additional Azure resources to Port that are not covered in the standard templates by using the Azure REST API reference. - [GCP](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp.md): Sync Google Cloud (GCP) resources into Port's software catalog. - [Cloud Function Sync protocol](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/cloud-function-sync.md): Sync resources from any HTTP endpoint using the GCP Cloud Function Sync protocol - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/examples.md): Base mapping examples for GCP resources including projects and generic cloud resources using the GCP integration. - [Map Extra Resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/examples/mapping_extra_resources.md): Learn how to map additional GCP resources to Port including compute instances, BigQuery datasets, and other Asset Inventory supported resources. - [Code quality & security](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security.md): Overview of code quality and security integrations for syncing data from tools like SonarQube, Snyk, Wiz, Mend.io, and Checkmarx to Port. - [Aikido](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/aikido.md): Sync Aikido security findings and vulnerabilities into Port's software catalog. - [ArmorCode](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/armorcode.md): Sync ArmorCode security findings and ASPM data into Port's software catalog. - [BrowserStack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/browserstack.md): Learn how to ingest BrowserStack Automate test data including plans, projects, and builds into Port using the no-code Ocean custom integration. - [Checkmarx One](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx.md): Sync Checkmarx One scan results and security findings into Port's software catalog. - [Resource mapping examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/examples.md): Mapping examples for Checkmarx resources including projects, scans, SAST, SCA, KICS, and API security findings with blueprints and configurations. - [Mend.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/mend.md): Sync Mend.io projects and SCA security findings into Port's software catalog. - [Snyk](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/snyk.md): Sync Snyk vulnerabilities, projects, and security findings into Port's software catalog. - [SonarQube](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube.md): Sync SonarQube code quality metrics, issues, and projects into Port's software catalog. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/examples.md): Mapping examples for SonarQube resources including projects and analysis results with blueprints and configurations. - [Wiz](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz.md): Sync Wiz cloud security issues and findings into Port's software catalog. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/examples.md): Mapping examples for Wiz resources including projects, cloud resources, issues, and control findings with blueprints and configurations. - [Kafka](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/event-processing/kafka.md): Sync Kafka clusters, topics, and brokers into Port's software catalog. - [LaunchDarkly](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/feature-management/launchdarkly.md): LaunchDarkly integration in Port - [Git](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git.md): Overview of Git provider integrations for syncing repositories, pull requests, and other data from GitHub, GitLab, Bitbucket, and Azure DevOps. - [Azure DevOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops.md): Sync Azure DevOps repositories, pull requests, and pipelines into Port's software catalog. - [Advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/advanced.md): Advanced Azure DevOps integration configuration including createMissingRelatedEntities, deleteDependentEntities, and custom settings. - [Capabilities](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/capabilities.md): Azure DevOps integration capabilities including file content enrichment for READMEs, CODEOWNERS, and documentation in entity properties. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/examples.md): Azure DevOps integration examples for repositories, pull requests, pipelines, and members with blueprint definitions and mapping configurations. - [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/gitops.md): Manage Port entities as code using port.yml files in Azure DevOps repositories. Define entities declaratively and sync via push events. - [Mapping Extensions](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/mapping_extensions.md): Extend Azure DevOps mapping with additional data sources beyond JQ. Includes file content enrichment and custom resource extraction. - [Bitbucket app (deprecated)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app.md): Deprecated Bitbucket Cloud app integration - use the newer Bitbucket Cloud integration instead for syncing repositories and pull requests. - [Advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/advanced.md): Advanced Bitbucket App configuration including createMissingRelatedEntities and deleteDependentEntities flags. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/examples.md): Bitbucket App integration examples for repositories, file contents, and pull requests with blueprint definitions and mapping configurations. - [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/gitops.md): Manage Port entities as code using port.yml files with Bitbucket App. Define entities declaratively and sync via push events. - [Bitbucket Cloud](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud.md): Sync Bitbucket Cloud repositories, pull requests, and projects into Port's software catalog. - [Advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/advanced.md): Advanced Bitbucket Cloud configuration including createMissingRelatedEntities and deleteDependentEntities flags. - [Capabilities](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/capabilities.md): Bitbucket Cloud integration capabilities including repository, pull request, and file ingestion with automatic blueprint creation. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/examples.md): Bitbucket Cloud integration examples for repositories, pull requests, projects, and files with blueprint definitions and mapping configurations. - [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/gitops.md): Manage Port entities as code using port.yml files in Bitbucket Cloud repositories. Define entities declaratively and sync automatically. - [Bitbucket (self-hosted)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server.md): Sync Bitbucket (self-hosted) repositories, pull requests, and projects into Port's software catalog. - [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/gitops.md): Manage Port entities as code using port.yml files in Bitbucket Server repositories. Define entities declaratively and sync via webhooks. - [GitHub (Ocean)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean.md): Sync GitHub repositories, pull requests, workflows, and deployments into Port's software catalog using the GitHub Ocean integration. - [Capabilities](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities.md): Advanced capabilities of the GitHub Ocean integration including parallel processing, file content ingestion, and custom query filtering. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/examples.md): Mapping examples for GitHub Ocean resources including repositories, pull requests, issues, deployments, releases, and team members. - [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/gitops.md): Manage Port entities as code using port.yml files in GitHub repositories. Define entities declaratively and sync via push events. - [Key changes](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/migration/key-changes.md): Overview of key changes between legacy GitHub app and GitHub Ocean including multi-org support, authentication, and mapping syntax updates. - [Migration steps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/migration/migration-steps.md): Customer checklist and step-by-step guide for migrating from the legacy GitHub app to GitHub Ocean before the October 15, 2026 cutover. - [GitLab v2](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2.md): Sync GitLab projects, merge requests, and pipelines into Port's software catalog using the v2 integration. - [Advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/advanced.md): Advanced GitLab v2 configuration including createMissingRelatedEntities, deleteDependentEntities, and custom integration flags. - [Capabilities](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities.md): GitLab v2 integration capabilities including file content enrichment for READMEs, CODEOWNERS, and documentation in entity properties. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/examples.md): Mapping examples for GitLab v2 resources including groups, projects, issues, merge requests, pipelines, and releases with blueprints and configurations. - [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/gitops.md): Manage Port entities as code using port.yml files in GitLab v2 repositories. Define entities declaratively and sync via push events. - [Mapping extensions](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/mapping_extensions.md): Extend GitLab v2 mapping with additional data sources beyond JQ. Includes file content enrichment and external API lookups. - [Migration guide to the v2 integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/migration.md): Migrate from GitLab v1 to v2 integration. Replace group tokens with service account authentication and update mapping configurations. - [GitLab](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab.md): Sync GitLab projects, merge requests, and pipelines into Port's software catalog. - [Advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/advanced.md): Advanced configuration options for the GitLab integration including delete dependents and entity creation flags. - [Capabilities](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/capabilities.md): Advanced capabilities of the GitLab integration including file ingestion, folder mapping, and live events configuration. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/examples.md): Mapping examples for GitLab resources including projects, merge requests, pipelines, issues, and groups with blueprints and configurations. - [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/gitops.md): Manage Port entities as code using port.yml files in GitLab repositories. Define entities declaratively and sync via push events. - [Mapping Extensions](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/mapping_extensions.md): Extend GitLab mapping with additional data sources beyond JQ. Includes file content enrichment and custom resource extraction. - [GitOps using Port CRDs](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitops-using-port-crd.md): Learn how to use GitOps with Port's Kubernetes CRDs to export custom entities from your K8s clusters to the catalog. - [Working with monorepos](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/working-with-monorepos.md): Learn how to configure Git integrations to create separate entities for each microservice in a monorepo. - [Microsoft Entra ID](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/azure-ad.md): Learn how to ingest Microsoft Entra ID (Azure AD) users, groups, applications, and service principals into Port. - [Okta](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/okta.md): Sync Okta users, groups, and applications into Port's software catalog. - [Incident management](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management.md): Overview of incident management integrations for syncing data from tools like PagerDuty, OpsGenie, and Incident.io to Port. - [FireHydrant](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/firehydrant.md): Sync FireHydrant incidents, services, and on-call data into Port's software catalog. - [Incident.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/incident-io.md): Learn how to ingest Incident.io incidents, alerts, schedules, and teams into Port using the no-code Ocean custom integration framework. - [Jira Service Management (JSM)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/jira-service-management.md): Learn how to ingest Jira Service Management service desks, customer requests, and organizations into Port using the no-code Ocean custom integration. - [Opsgenie](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie.md): Sync Opsgenie alerts, incidents, services, and on-call schedules into Port's software catalog. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/examples.md): Mapping examples for OpsGenie resources including users, teams, services, alerts, and incidents with blueprints and configurations. - [PagerDuty](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty.md): Sync PagerDuty services, incidents, and on-call schedules into Port's software catalog. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/examples.md): Mapping examples for PagerDuty resources including users, teams, services, incidents, and schedules with blueprints and configurations. - [Rootly](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/rootly.md): Learn how to ingest Rootly incidents, services, teams, and on-call data into Port using the no-code Ocean custom integration framework. - [ServiceNow](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/servicenow.md): Sync ServiceNow incidents, services, and CMDB data into Port's software catalog. - [Statuspage](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage.md): Sync Statuspage pages, components, and incidents into Port's software catalog. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/examples.md): Mapping examples for Statuspage resources including status pages, components, incidents, and maintenance windows with blueprints and configurations. - [Komodor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/komodor.md): Sync Komodor Kubernetes services, deployments, and health data into Port's software catalog. - [Kubernetes](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes.md): Sync Kubernetes workloads, namespaces, and resources into Port's software catalog. - [Advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/advanced.md): Advanced configuration options for the Kubernetes exporter including resync intervals, state keys, and delete behavior parameters. - [Basic example](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/basic-example.md): Basic Example - [Custom CRDs](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/custom-crds.md): Learn how to export and map custom Kubernetes CRDs like ArgoCD resources to Port using the K8s exporter. - [Port Entity CRD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/port-crd.md): Learn how to use Port's Kubernetes Custom Resource Definitions to define and sync entities from K8s cluster manifests to your catalog. - [Templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates.md): Pre-built configuration templates for common Kubernetes exporter use cases and third-party tools like ArgoCD and Istio. - [FluxCD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/fluxcd.md): FluxCD quickstart - [Kubernetes (advanced)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/full-kubernetes-exporter.md): Advanced Kubernetes installation - [Istio](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/istio.md): Istio quickstart - [Knative](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/knative.md): Knative quickstart - [Kyverno](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/kyverno.md): Kyverno quickstart - [Red Hat Openshift](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/openshift.md): Openshift quickstart - [Trivy Operator](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/trivy.md): Trivy quickstart - [Amplication](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/amplication.md): Sync Amplication services and generated resources into Port's software catalog. - [Backstage](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/backstage.md): Ingest Backstage entities into your context lake so that agents, workflows, and your team members can access and act on them. - [Slack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/slack.md): Learn how to ingest Slack users, channels, and user groups into Port using the no-code Ocean custom integration framework. - [Project management](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management.md): Overview of project management integrations for syncing data from tools like Jira, Linear, and other project tracking systems to Port. - [Jira Server (self-hosted)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server.md): Sync Jira Server (self-hosted) projects and issues into Port's software catalog. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/examples.md): Mapping examples for Jira Server resources including projects, users, and issues with default blueprints and configurations. - [Jira](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira.md): Sync Jira projects, issues, and teams into Port's software catalog. - [Capabilities](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/capabilities.md): Advanced capabilities of the Jira integration including JQL filtering, field selection, live events, and sprints ingestion. - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/examples.md): Mapping examples for Jira Cloud resources including issues, epics, sprints, boards, teams, and components with blueprints and configurations. - [Linear](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/linear.md): Sync Linear teams, issues, labels, documents, users, projects, initiatives, workflow states, cycles, and team memberships into Port's software catalog. - [Terraform Cloud & Terraform Enterprise](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/terraform-cloud.md): Terraform integration in Port - [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/terraform-cloud/examples.md): Mapping examples for Terraform Cloud resources including organizations, workspaces, runs, and state versions with blueprints and configurations. - [Actions processing](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/actions-processing.md): How Ocean integrations claim, queue, and execute integration action runs from Port, including partitioning, rate limiting, flow control, and actionsProcessor configuration. - [Advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration.md): Configure Ocean integration endpoints, health checks, proxies, custom CA certificates, SSL verification, HTTP timeouts and retries, and the outbound IP blocker for self-hosted deployments. - [Connection probe](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/connection-probe.md): Use the ocean probe command to check that an Ocean integration can connect to the third-party tool and has the permissions required for each kind. - [Event listeners and resync triggers](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners.md): How Ocean event listeners (POLLING, KAFKA, WEBHOOK, ONCE, WEBHOOKS_ONLY, ACTIONS_ONLY) receive resync requests from Port, and how scheduled resyncs work. - [Identity propagation](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/identity-propagation.md): Run Ocean integration actions in workflows as the user who triggered them, using an OAuth broker, a token vault, and Port-signed identity tokens. - [Live events processing](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/live-events-processing.md): How Ocean receives, queues, routes, authenticates, and retries live events from third-party tools before sending the updated data to Port. - [Ocean framework overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/overview.md): How Ocean, the open source framework behind Port integrations, runs an integration, triggers resyncs, processes live events, and executes actions. - [Install a native integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview.md): Install Port native integrations to sync GitHub, Kubernetes, Jira, and more into your catalog with hosted or self-hosted options. - [Full resync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync.md): How Port full resync fetches, maps, and ingests integration data, including reconciliation and monitoring sync status. - [Incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync.md): How Port incremental sync fetches only created or updated records between full resyncs. - [Live events](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events.md): How Port live events process webhook and watch API changes in real time, including scalable self-hosted infrastructure with Redis Streams and ocean-gateway. - [Sync interactions](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/sync-interactions.md): How Port handles overlapping full resyncs, apply mapping runs, incremental syncs, and live events when more than one sync-related process runs at the same time. ##### other - [Integrate assets using GitOps](/context-lake/ingestion/ingest-data-into-port/other/gitops.md): Overview of GitOps integration options for managing Port entities using your Git repositories as the source of truth, including how to build a custom GitOps integration using Port's API. - [IaC](/context-lake/ingestion/ingest-data-into-port/other/iac.md): Overview of Infrastructure-as-Code methods for ingesting data and defining your software catalog alongside IaC configurations using Terraform or Pulumi. - [Pulumi](/context-lake/ingestion/ingest-data-into-port/other/iac/pulumi.md): Learn how to use Port's Pulumi provider to manage blueprints and entities as infrastructure-as-code in Python, TypeScript, JavaScript, or Go. - [Terraform](/context-lake/ingestion/ingest-data-into-port/other/iac/terraform.md): Learn how to use Port's Terraform provider to manage blueprints, entities, and other Port resources as infrastructure-as-code. - [Manual entity management](/context-lake/ingestion/ingest-data-into-port/other/manual-entity-management.md): Not every catalog entity has a single clean source of truth. Manual entity management helps you fill gaps, correct edge cases, and enrich data that automation cannot reliably provide. - [Migrate Blueprint Data](/context-lake/ingestion/ingest-data-into-port/other/migrate-data.md): Learn how to migrate entity data between blueprints when evolving your software catalog structure, including property transformations and relation updates. - [Other ingestion methods](/context-lake/ingestion/ingest-data-into-port/other/other-ingestion-methods-overview.md): Explore Port's other ingestion options: IaC, GitOps, S3 buckets, Port MCP, and manual updates when continuous sync is not ideal. - [Port MCP](/context-lake/ingestion/ingest-data-into-port/other/port-mcp.md): You can ingest entities into your catalog using Port MCP tools. This is useful when an agent or an IDE workflow needs to create or update entities as part of a multi-step SDLC automation. - [S3 Bucket](/context-lake/ingestion/ingest-data-into-port/other/s3-integrations.md): Learn how to ingest data from external sources into Port using Airbyte or Fivetran with S3 as an intermediate storage layer. ##### webhook - [Examples](/context-lake/ingestion/ingest-data-into-port/webhook/examples.md): Examples of using Port's webhook integration to ingest resources from various tools like Jira, OpsGenie, SonarQube, and Sentry. - [Codecov](/context-lake/ingestion/ingest-data-into-port/webhook/examples/codecov.md): Ingest CodeCov coverage into your catalog - [Falco Sidekick](/context-lake/ingestion/ingest-data-into-port/webhook/examples/falco.md): Ingest Falco alerts into your catalog - [Grafana](/context-lake/ingestion/ingest-data-into-port/webhook/examples/grafana.md): Ingest Grafana alerts into your catalog - [JFrog](/context-lake/ingestion/ingest-data-into-port/webhook/examples/jfrog.md): Ingest JFrog artifacts, Docker tags and builds into your catalog - [Prometheus](/context-lake/ingestion/ingest-data-into-port/webhook/examples/prometheus.md): Ingest Prometheus alerts into your catalog - [Split](/context-lake/ingestion/ingest-data-into-port/webhook/examples/split.md): Ingest Split IO Impressions into your catalog - [StackHawk](/context-lake/ingestion/ingest-data-into-port/webhook/examples/stackhawk.md): Ingest StackHawk application vulnerabilities into Port - [Ingest via Webhooks](/context-lake/ingestion/ingest-data-into-port/webhook/ingest-via-webhooks-overview.md): Use Port's generic webhook to ingest events from any service, map payloads with JQ, and create, update, or delete entities. #### integrations-vs-mcp Port gives you two ways to connect external tools: integrations and MCP connectors. They solve different problems and are designed to complement each other — in many cases, you can benefit from both connected for the same tool. - [Integrations vs. MCP connectors](/context-lake/ingestion/integrations-vs-mcp.md): Port gives you two ways to connect external tools: integrations and MCP connectors. They solve different problems and are designed to complement each other — in many cases, you can benefit from both connected for the same tool. #### overview Choose how you ingest into Port's Context Lake: native integrations, MCP connectors, APIs, webhooks, or custom integrations. - [Overview](/context-lake/ingestion/overview.md): Choose how you ingest into Port's Context Lake: native integrations, MCP connectors, APIs, webhooks, or custom integrations. ### overview See how Port's context lake unifies data from your engineering tools into one queryable layer that humans and AI agents can use. - [Context Lake](/context-lake/overview.md): See how Port's context lake unifies data from your engineering tools into one queryable layer that humans and AI agents can use. ## Workflows ### overview Build Port workflows for humans, automations, and AI agents. Trigger runs from forms, catalog events, schedules, or Port MCP. - [Workflows & tools](/workflows/overview.md): Build Port workflows for humans, automations, and AI agents. Trigger runs from forms, catalog events, schedules, or Port MCP. ### build-workflows #### configure-and-test ##### data-flow Pass data between workflow nodes using outputs and variables, reference trigger inputs, and use JQ expressions for data transformation. - [Data flow](/workflows/build-workflows/configure-and-test/data-flow.md): Pass data between workflow nodes using outputs and variables, reference trigger inputs, and use JQ expressions for data transformation. ##### reusable-workflows Share logic across workflows by triggering child workflows from parent workflows, enabling reusable approval flows and shared provisioning sequences. - [Reusable workflows](/workflows/build-workflows/configure-and-test/reusable-workflows.md): Share logic across workflows by triggering child workflows from parent workflows, enabling reusable approval flows and shared provisioning sequences. ##### setup-checklist Resolve a saved workflow's missing integrations and secrets from the workflow editor. - [Setup checklist](/workflows/build-workflows/configure-and-test/setup-checklist.md): Resolve a saved workflow's missing integrations and secrets from the workflow editor. ##### test-workflows Test your workflow directly from the builder by running the entire workflow or individual nodes with mock data before publishing. - [Test workflows](/workflows/build-workflows/configure-and-test/test-workflows.md): Test your workflow directly from the builder by running the entire workflow or individual nodes with mock data before publishing. #### iac Manage Port workflows as code with the Terraform or Pulumi providers, defining workflow nodes and connections. - [Manage workflows with IaC](/workflows/build-workflows/iac.md): Manage Port workflows as code with the Terraform or Pulumi providers, defining workflow nodes and connections. #### nodes ##### action-nodes - [AI](/workflows/build-workflows/nodes/action-nodes/ai.md): Invoke Port AI or AI agents directly from workflows to perform intelligent operations and automated decision-making. - [Configuration and outputs](/workflows/build-workflows/nodes/action-nodes/configuration-and-outputs.md): Learn common action node configuration fields, outputs, and how to chain action nodes together in workflows. - [Claude managed agents](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/claude-managed-agents.md): The Claude managed agents integration action allows workflows to create new agents or trigger agent sessions directly using your installed Claude Managed Agents integration. - [Cursor cloud agents](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/cursor-cloud-agents.md): The Cursor cloud agents integration action allows workflows to launch new Cursor cloud agents or send follow-up prompts to existing ones directly using your installed Cursor Cloud Agents integration. - [Azure DevOps Pipelines](/workflows/build-workflows/nodes/action-nodes/integration-actions/git/azure-devops.md): Trigger Azure DevOps pipelines and manage pull requests from Port workflows using your installed Azure DevOps integration. - [GitHub](/workflows/build-workflows/nodes/action-nodes/integration-actions/git/github.md): Trigger GitHub Actions workflows, manage pull requests, and create issues from Port workflows using your installed GitHub integration. - [GitLab](/workflows/build-workflows/nodes/action-nodes/integration-actions/git/gitlab.md): Trigger GitLab CI/CD pipelines, create and update merge requests, post merge request comments, and set comment reactions from Port workflows using your installed GitLab integration. - [PagerDuty](/workflows/build-workflows/nodes/action-nodes/integration-actions/incident-management/pagerduty.md): Trigger, acknowledge, and resolve PagerDuty incidents from Port workflows using your installed PagerDuty integration. - [Overview](/workflows/build-workflows/nodes/action-nodes/integration-actions/overview.md): Trigger operations in connected platforms like GitHub directly from workflows using Port's existing integrations. - [Jira](/workflows/build-workflows/nodes/action-nodes/integration-actions/project-management/jira.md): Create Jira issues and change issue status from Port workflows using your installed Jira integration. - [Linear](/workflows/build-workflows/nodes/action-nodes/integration-actions/project-management/linear.md): Create, update, delegate to agent, archive, delete, comment on, and react to Linear issues, and add documents from Port workflows. - [Kafka](/workflows/build-workflows/nodes/action-nodes/kafka.md): Publish messages to Port's dedicated Kafka topic from workflows to process events with your own consumers. - [Port execution agent](/workflows/build-workflows/nodes/action-nodes/port-execution-agent.md): Execute workflow webhook actions inside your private network without exposing public endpoints using the Port execution agent. - [Upsert entity](/workflows/build-workflows/nodes/action-nodes/upsert-entity.md): Create or update entities in Port's software catalog from workflows using the upsert entity action node. - [Webhook](/workflows/build-workflows/nodes/action-nodes/webhook.md): Send HTTP requests to external endpoints from workflows, supporting synchronous and asynchronous operations with custom headers and payloads. ##### flow-nodes - [Condition nodes](/workflows/build-workflows/nodes/flow-nodes/condition.md): Add branching logic to workflows using condition nodes that evaluate JQ expressions and route execution to different paths. - [Input nodes](/workflows/build-workflows/nodes/flow-nodes/input.md): Add human-in-the-loop steps to workflows using input nodes for approvals, manual gates, and decision points that require user responses. #### quickstart Create your first workflow in Port using the visual editor, JSON editor, or AI-assisted builder to send a webhook notification. - [Quickstart](/workflows/build-workflows/quickstart.md): Create your first workflow in Port using the visual editor, JSON editor, or AI-assisted builder to send a webhook notification. #### triggers ##### event-trigger Configure workflows to run automatically when entities are created, updated, or deleted in your software catalog. - [Event trigger](/workflows/build-workflows/triggers/event-trigger.md): Configure workflows to run automatically when entities are created, updated, or deleted in your software catalog. ##### schedule-trigger Configure workflows to run automatically on a recurring cron schedule. - [Schedule trigger](/workflows/build-workflows/triggers/schedule-trigger.md): Configure workflows to run automatically on a recurring cron schedule. ##### self-service-trigger - [Advanced input configurations](/workflows/build-workflows/triggers/self-service-trigger/advanced-input-configurations.md): Configure advanced user input features including visibility controls, input dependencies, and dynamic default values using JQ expressions. - [Configuration](/workflows/build-workflows/triggers/self-service-trigger/configuration.md): Configure self-service trigger nodes with user inputs, permissions, and UI contexts to create on-demand workflow executions. - [Contexts](/workflows/build-workflows/triggers/self-service-trigger/contexts.md): Surface self-service triggers in additional UI locations like entity creation flows and action menus using the contexts configuration. - [Share a pre-filled workflow URL](/workflows/build-workflows/triggers/self-service-trigger/send-link-to-workflow.md): Generate shareable URLs with pre-filled workflow inputs using JSURL encoding. Useful for bookmarks, documentation, and programmatic links. - [Array](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/array.md): Configure array inputs for workflow forms to accept lists of values like tags, services to deploy, or email recipients. - [Datetime](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/datetime.md): Configure datetime inputs for workflow forms to accept date and time values for scheduling, expiration, or maintenance windows. - [Email](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/email.md): Configure email inputs for workflow forms to accept and validate email addresses for contacts or notifications. - [Entity](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/entity.md): Configure entity inputs for workflow forms to let users select entities from your software catalog like services or environments. - [Markdown](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/markdown.md): Configure markdown inputs for workflow forms to accept formatted content like release notes, runbooks, or descriptions. - [Number](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/number.md): Configure number inputs for workflow forms to accept numeric values like replica counts, port numbers, or resource limits. - [Object](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/object.md): Configure object inputs for workflow forms to accept JSON objects for configuration data, metadata, or key-value pairs. - [Structure and fields](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/structure-and-fields.md): Define user inputs for self-service workflows including input structure, common fields, and required vs optional configurations. - [Team](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/team.md): Configure team inputs for workflow forms to let users select Port teams for ownership assignment or notifications. - [Text](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/text.md): Configure text inputs for workflow forms to accept string values like service names, version tags, or commit SHAs. - [Toggle](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/toggle.md): Configure toggle inputs for workflow forms to accept boolean true/false values for feature flags or operation modes. - [URL](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/url.md): Configure URL inputs for workflow forms to accept and validate URL strings for repository links, API endpoints, or documentation. - [User](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/user.md): Configure user inputs for workflow forms to let users select Port users for ownership, reviewers, or on-call assignment. - [YAML](/workflows/build-workflows/triggers/self-service-trigger/user-inputs/yaml.md): Configure YAML inputs for workflow forms to accept YAML-formatted content like Kubernetes manifests or configuration files. ### actions-and-automations #### create-self-service-experiences Create self-service actions that let developers trigger workflows from Port, such as scaffolding services, provisioning resources, and running day-2 operations, with forms, guardrails, and approvals. - [Self-service actions](/workflows/actions-and-automations/create-self-service-experiences.md): Create self-service actions that let developers trigger workflows from Port, such as scaffolding services, provisioning resources, and running day-2 operations, with forms, guardrails, and approvals. ##### send-link-to-action Generate shareable URLs with pre-filled action inputs using JSURL encoding. Useful for bookmarks, documentation, and programmatic links. - [Share a pre-filled action URL](/workflows/actions-and-automations/create-self-service-experiences/send-link-to-action.md): Generate shareable URLs with pre-filled action inputs using JSURL encoding. Useful for bookmarks, documentation, and programmatic links. ##### set-self-service-actions-rbac Control who can execute self-service actions using role-based permissions. Configure manual approval workflows and ownership-based restrictions. - [Set actions RBAC](/workflows/actions-and-automations/create-self-service-experiences/set-self-service-actions-rbac.md): Control who can execute self-service actions using role-based permissions. Configure manual approval workflows and ownership-based restrictions. - [Dynamic permissions](/workflows/actions-and-automations/create-self-service-experiences/set-self-service-actions-rbac/dynamic-permissions.md): Control action execution and approval using runtime data from your catalog. Define queries and JQ conditions for ownership, manager approval, and on-call rules. - [Examples](/workflows/actions-and-automations/create-self-service-experiences/set-self-service-actions-rbac/dynamic-permissions/examples.md): Dynamic permissions examples including blocking duplicate entities, requiring manager approval, and enforcing ownership-based execution rules. ##### setup-the-backend Configure self-service action backends to execute logic when triggered. Supports GitHub, GitLab, Azure DevOps, Jenkins, webhooks, and more. - [Set up backend](/workflows/actions-and-automations/create-self-service-experiences/setup-the-backend.md): Configure self-service action backends to execute logic when triggered. Supports GitHub, GitLab, Azure DevOps, Jenkins, webhooks, and more. ##### setup-ui-for-action Configure self-service action frontend including title, icon, operation type (create, day-2, delete), and user form inputs. - [Set up frontend](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action.md): Configure self-service action frontend including title, icon, operation type (create, day-2, delete), and user form inputs. - [Advanced input configurations](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/advanced-form-configurations.md): Advanced input settings allow you to create more customizable experiences for users who perform self-service actions. This is done by creating adaptive inputs that change according to data about the entity, the user, and other inputs. - [User Inputs](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs.md): Define user inputs for self-service actions with various types, validation rules, default values, and conditional visibility using jqQuery. - [Array](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/array.md): Array is an input for lists of data - [Datetime](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/datetime.md): Datetime is an input used to reference a date and time - [Email](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/email.md): Email is an input used to save Email addresses - [Entity](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/entity.md): Entity is an input used to reference existing entities from the software catalog when triggering actions - [Number](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/number.md): Number is a basic input for numeric data - [Object](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/object.md): Object is a basic input for JSON data - [Secret](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/secret.md): A secret input encrypts its value before sending it to your backend and never stores or logs it. Choose Port-managed encryption or your own public key. - [Team](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/team.md): Team is an input used to reference teams that exist in Port - [Text](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/text.md): Text is a basic input for textual information - [Toggle (Boolean)](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/toggle.md): Toggle is a basic input that has one of two possible values - true and false - [URL](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/url.md): URL is an input used to save links to websites - [User](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/user.md): User is an input used to reference users that exist in Port - [Yaml](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/yaml.md): Yaml is an input used to save object definitions in YAML #### define-automations Create Port automations that trigger actions automatically based on catalog events like entity creation, updates, timers, or external webhooks. Configure triggers, conditions, and backend handlers. - [Define automations](/workflows/actions-and-automations/define-automations.md): Create Port automations that trigger actions automatically based on catalog events like entity creation, updates, timers, or external webhooks. Configure triggers, conditions, and backend handlers. ##### examples Automation examples including Slack/Teams notifications on entity changes, TTL enforcement, PagerDuty incident creation, and scorecard-based alerts. - [Examples](/workflows/actions-and-automations/define-automations/examples.md): Automation examples including Slack/Teams notifications on entity changes, TTL enforcement, PagerDuty incident creation, and scorecard-based alerts. ##### migrate-from-changelog-destination Step-by-step guide for migrating from the legacy changelogDestination feature to Port Automations. - [Migrate from changelog destination](/workflows/actions-and-automations/define-automations/migrate-from-changelog-destination.md): Step-by-step guide for migrating from the legacy changelogDestination feature to Port Automations. ##### setup-action Configure automation backends including webhook, GitHub, GitLab, and Kafka. Define JSON payloads with jq expressions and access trigger event data. - [Set up backend](/workflows/actions-and-automations/define-automations/setup-action.md): Configure automation backends including webhook, GitHub, GitLab, and Kafka. Define JSON payloads with jq expressions and access trigger event data. ##### setup-trigger Configure automation triggers for entity events (create, update, delete, timer expiration) or action run status changes. Supports JQ conditions for filtering. - [Set up trigger](/workflows/actions-and-automations/define-automations/setup-trigger.md): Configure automation triggers for entity events (create, update, delete, timer expiration) or action run status changes. Supports JQ conditions for filtering. #### overview Understand Port's legacy actions and automations so you can run self-service and event-based logic, or move new work to workflows. - [Overview](/workflows/actions-and-automations/overview.md): Understand Port's legacy actions and automations so you can run self-service and event-based logic, or move new work to workflows. #### reflect-action-progress Once a self-service action or automation has been executed, an actionRun object will be created in Port. - [Interact with action runs](/workflows/actions-and-automations/reflect-action-progress.md): Once a self-service action or automation has been executed, an actionRun object will be created in Port. #### setup-backend Configure backends for self-service actions including GitHub workflows, GitLab pipelines, Azure DevOps, Jenkins, webhooks, and Kafka topics. - [Backend types](/workflows/actions-and-automations/setup-backend.md): Configure backends for self-service actions including GitHub workflows, GitLab pipelines, Azure DevOps, Jenkins, webhooks, and Kafka topics. ##### azure-pipeline The Azure backend can trigger Azure pipelines for both self-service actions and automations, using incoming webhook triggers. - [Azure pipeline](/workflows/actions-and-automations/setup-backend/azure-pipeline.md): The Azure backend can trigger Azure pipelines for both self-service actions and automations, using incoming webhook triggers. - [Trigger pipelines in non-default branch](/workflows/actions-and-automations/setup-backend/azure-pipeline/trigger-non-default-branch.md): Configure Port self-service actions to trigger Azure DevOps pipelines from non-default branches using refName parameter. - [Azure DevOps troubleshooting](/workflows/actions-and-automations/setup-backend/azure-pipeline/troubleshooting.md): Common questions and solutions for Azure DevOps integration and pipelines in Port. ##### create-update-entity In some cases, we don't want to run complex logic via a workflow or pipeline, but rather want our backend to simply create or update an entity in our software catalog. - [Create/update entity](/workflows/actions-and-automations/setup-backend/create-update-entity.md): In some cases, we don't want to run complex logic via a workflow or pipeline, but rather want our backend to simply create or update an entity in our software catalog. ##### github-ocean The GitHub Ocean backend allows you to run GitHub integration actions for your self-service actions and automations, including triggering GitHub Actions workflows and managing pull requests, using the GitHub Ocean integration. - [GitHub workflow via Ocean](/workflows/actions-and-automations/setup-backend/github-ocean.md): The GitHub Ocean backend allows you to run GitHub integration actions for your self-service actions and automations, including triggering GitHub Actions workflows and managing pull requests, using the GitHub Ocean integration. ##### github-workflow The GitHub backend allows you to trigger GitHub workflows for your self-service actions and automations, using Port's GitHub application. - [GitHub workflow](/workflows/actions-and-automations/setup-backend/github-workflow.md): The GitHub backend allows you to trigger GitHub workflows for your self-service actions and automations, using Port's GitHub application. ##### gitlab-pipeline The GitLab backend allows you to trigger GitLab pipelines for your self-service actions and automations. - [GitLab pipeline](/workflows/actions-and-automations/setup-backend/gitlab-pipeline.md): The GitLab backend allows you to trigger GitLab pipelines for your self-service actions and automations. - [GitLab SaaS](/workflows/actions-and-automations/setup-backend/gitlab-pipeline/saas.md): Trigger GitLab SaaS or reachable self-hosted GitLab pipelines directly from Port using webhook URLs with trigger tokens, without an execution agent. - [Self-hosted GitLab](/workflows/actions-and-automations/setup-backend/gitlab-pipeline/self-hosted.md): Choose direct webhooks for reachable self-hosted GitLab endpoints, or use the Port execution agent for private endpoints with Kafka or HTTP polling. ##### jenkins-pipeline The Jenkins backend allows you to trigger Jenkins pipelines for your self-service actions and automations, using webhooks. - [Jenkins pipeline](/workflows/actions-and-automations/setup-backend/jenkins-pipeline.md): The Jenkins backend allows you to trigger Jenkins pipelines for your self-service actions and automations, using webhooks. ##### kafka Port manages a Kafka topic per customer that publishes the execution run requests. - [Kafka topic](/workflows/actions-and-automations/setup-backend/kafka.md): Port manages a Kafka topic per customer that publishes the execution run requests. ##### security Port IP addresses for firewall allow lists. Covers webhook outbound IPs and Ocean integration source IPs for EU and US regions. - [Security](/workflows/actions-and-automations/setup-backend/security.md): Port IP addresses for firewall allow lists. Covers webhook outbound IPs and Ocean integration source IPs for EU and US regions. ##### send-slack-message The Send Slack message backend type allows you to send a message to a Slack channel, using a webhook URL. - [Send Slack message](/workflows/actions-and-automations/setup-backend/send-slack-message.md): The Send Slack message backend type allows you to send a message to a Slack channel, using a webhook URL. ##### webhook The webhook backend allows you to trigger your own custom webhooks, for both self-service actions and automations. - [Webhook](/workflows/actions-and-automations/setup-backend/webhook.md): The webhook backend allows you to trigger your own custom webhooks, for both self-service actions and automations. - [Circle CI Actions](/workflows/actions-and-automations/setup-backend/webhook/circle-ci.md): Port's Circle CI integration allows you to trigger new pipelines in Circle CI directly from Port. - [Triggering Cloud Build using webhooks](/workflows/actions-and-automations/setup-backend/webhook/cloudbuild-pipeline.md): This page will guide you through the process of triggering your Cloud Build pipelines from Port, using Webhook Actions. - [Software templates](/workflows/actions-and-automations/setup-backend/webhook/examples/software-templates.md): Use Cookiecutter with Port webhooks to scaffold new service repositories from software templates via self-service actions. - [Terraform: No-Code resource provisioning](/workflows/actions-and-automations/setup-backend/webhook/examples/terraform-no-code-resource-provisioning.md): Expose Terraform-based cloud resource provisioning as no-code self-service actions. Example covers S3 bucket lifecycle operations. - [Debug webhooks locally](/workflows/actions-and-automations/setup-backend/webhook/local-debugging-webhook.md): Debug webhook self-service actions locally using smee.io to receive Port webhook payloads on your development machine. - [Port execution agent](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent.md): Port execution agent forwards action invocations to private endpoints using Kafka or HTTP polling, without exposing public URLs. - [Control the payload](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/control-the-payload.md): Port offers two ways to control the payload sent to your backend: - [Argo workflow](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/examples/trigger-an-argo-workflow.md): This example helps internal developer teams to trigger an Argo Workflow using Port's self service actions. We will create a blueprint for argoWorkflow that will be connected to a backend action. We will then add some configuration files (invocations.json) to control the payload and trigger your Argo Workflows directly from Port using the sync execution method. - [CircleCI pipline](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/examples/trigger-circleCI-pipeline.md): This example helps internal developer teams to trigger CircleCI pipelines using Port's self service actions. In particular, you will create a blueprint for circleCI Project that will be connected to a backend action. You will then add some configuration files (invocations.json) to control the payload and trigger your CircleCI pipeline directly from Port using the sync execution method. - [OpsGenie incident](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/examples/trigger-opsgenie-incident.md): This example helps internal developer teams to trigger Opsgenie incidents using Port's self service actions. In particular, you will create a blueprint for opsgenieIncident that will be connected to a backend action. You will then add some configuration files (invocations.json) to control the payload and trigger your Opsgenie incident directly from Port using the sync execution method. - [Terraform Cloud run](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/examples/trigger-terraform-cloud-run.md): This example helps internal developer teams to trigger an Terraform Cloud run using Port's self service actions. We will create a blueprint for Terraform Cloud Workspace that will be connected to a backend action. We will then add some configuration files (invocations.json) to control the payload and trigger your Terraform Cloud run directly from Port using the sync execution method. - [Windmill job](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/examples/trigger-windmill-job.md): This example helps internal developer teams to trigger Windmill job using Port's self service actions. In particular, you will create a blueprint for windmillJob that will be connected to a backend action. You will then add some configuration files (invocations.json) to control the payload and trigger your Windmill job directly from Port using async/sync execution method. - [ArgoCD](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/installation-methods/argocd.md): Install the Port execution agent in Kubernetes using ArgoCD with Helm capabilities. Includes manifest examples and configuration steps. - [Docker](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/installation-methods/docker.md): Run the Port execution agent as a Docker container with Kafka streaming to forward action invocations to private endpoints. - [Helm](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/installation-methods/helm.md): Install the Port execution agent in Kubernetes using Helm. Configure Kafka credentials and optional GitLab pipeline triggers. - [Usage](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/usage.md): Configure actions to use the Port execution agent by setting type to WEBHOOK and agent to true. Supports Kafka and HTTP polling streamers. - [Validate webhook signatures](/workflows/actions-and-automations/setup-backend/webhook/signature-verification.md): Verify webhook signatures using x-port-timestamp and x-port-signature headers to ensure requests are from Port and not tampered with. - [Terraform cloud actions](/workflows/actions-and-automations/setup-backend/webhook/terraform-cloud.md): Port's Terraform Cloud integration allows you to trigger Terraform Cloud runs directly from Port. ### concepts Learn the core building blocks of workflows including nodes (triggers, actions, conditions), edges, and how multiple triggers work together. - [Concepts and structure](/workflows/concepts.md): Learn the core building blocks of workflows including nodes (triggers, actions, conditions), edges, and how multiple triggers work together. ### expose-as-tool Make any workflow invokable by AI agents through the Port MCP server. Learn how to configure tool names, write descriptions agents can use, and control who can invoke the workflow. - [Expose workflows as tools](/workflows/expose-as-tool.md): Make any workflow invokable by AI agents through the Port MCP server. Learn how to configure tool names, write descriptions agents can use, and control who can invoke the workflow. ### permissions-and-rbac #### manage-workflows Create a workflow and set who can build it and who can run it, through Port AI, the UI, and the API. - [Manage a workflow](/workflows/permissions-and-rbac/manage-workflows.md): Create a workflow and set who can build it and who can run it, through Port AI, the UI, and the API. #### permissions Control who can see and execute workflows using team-based, role-based, or dynamic permission rules on self-service triggers. - [Workflow execute permissions](/workflows/permissions-and-rbac/permissions.md): Control who can see and execute workflows using team-based, role-based, or dynamic permission rules on self-service triggers. #### workflow-catalog Workflows are represented as entities in the _workflow system blueprint, enabling team ownership and catalog-level access control. - [Workflow catalog and ownership](/workflows/permissions-and-rbac/workflow-catalog.md): Workflows are represented as entities in the _workflow system blueprint, enabling team ownership and catalog-level access control. ### track-workflow-execution #### track-and-manage-runs Monitor workflow executions, view run status and logs, cancel running workflows, and interact with workflow runs through the API. - [Track & manage runs](/workflows/track-workflow-execution/track-and-manage-runs.md): Monitor workflow executions, view run status and logs, cancel running workflows, and interact with workflow runs through the API. #### workflow-run-page View and debug individual workflow executions with visual status indicators showing node success, failure, and data flow through the graph. - [Workflow run page](/workflows/track-workflow-execution/workflow-run-page.md): View and debug individual workflow executions with visual status indicators showing node success, failure, and data flow through the graph. ### use-cases-and-examples Real-world workflow examples for common use cases including scaffolding microservices, deployments, automated catalog updates, and agent-triggered rollbacks. - [Use-cases & examples](/workflows/use-cases-and-examples.md): Real-world workflow examples for common use cases including scaffolding microservices, deployments, automated catalog updates, and agent-triggered rollbacks. ## port-ai ### ai-invocations Track, debug, and collect feedback on every Port AI interaction using AI invocation records. - [AI invocations](/port-ai/ai-invocations.md): Track, debug, and collect feedback on every Port AI interaction using AI invocation records. ### api-interaction Port AI can be accessed programmatically through Port's API, enabling integration into custom applications and workflows. - [Port AI API interaction](/port-ai/api-interaction.md): Port AI can be accessed programmatically through Port's API, enabling integration into custom applications and workflows. ### context-engineering Context engineering is the practice of constructing an optimal information environment that provides the right amount of directions, tools, and knowledge for AI systems to successfully solve problems. - [Context engineering](/port-ai/context-engineering.md): Context engineering is the practice of constructing an optimal information environment that provides the right amount of directions, tools, and knowledge for AI systems to successfully solve problems. ### interfaces #### port-cli Run Port from the shell. Coding agents print JSON, pipe it through jq, and batch updates. Platform engineers export, compare, and migrate organizations. - [Port CLI](/port-ai/interfaces/port-cli.md): Run Port from the shell. Coding agents print JSON, pipe it through jq, and batch updates. Platform engineers export, compare, and migrate organizations. ### limits-and-quotas Rate limits, monthly quotas, error handling, and technical limitations for Port AI across all interfaces. - [Limits and quotas](/port-ai/limits-and-quotas.md): Rate limits, monthly quotas, error handling, and technical limitations for Port AI across all interfaces. ### llm-providers-management #### overview Manage and configure LLM providers that power Port AI, including Port's managed infrastructure and bring-your-own-LLM options. - [LLM provider management](/port-ai/llm-providers-management/overview.md): Manage and configure LLM providers that power Port AI, including Port's managed infrastructure and bring-your-own-LLM options. #### providers ##### anthropic-compatible Configure any Anthropic Messages API-compatible endpoint as an LLM provider in Port, including custom gateways and proxies. - [Anthropic compatible setup](/port-ai/llm-providers-management/providers/anthropic-compatible.md): Configure any Anthropic Messages API-compatible endpoint as an LLM provider in Port, including custom gateways and proxies. ##### anthropic Configure Anthropic as an LLM provider in Port to use Claude models directly through Anthropic's API. - [Anthropic setup](/port-ai/llm-providers-management/providers/anthropic.md): Configure Anthropic as an LLM provider in Port to use Claude models directly through Anthropic's API. ##### aws-bedrock Configure AWS Bedrock as an LLM provider in Port using IAM-based authentication to access Claude and other models. - [AWS Bedrock setup](/port-ai/llm-providers-management/providers/aws-bedrock.md): Configure AWS Bedrock as an LLM provider in Port using IAM-based authentication to access Claude and other models. ##### azure-anthropic Configure Azure Anthropic as an LLM provider in Port to use Claude models hosted in your Azure deployment. - [Azure Anthropic setup](/port-ai/llm-providers-management/providers/azure-anthropic.md): Configure Azure Anthropic as an LLM provider in Port to use Claude models hosted in your Azure deployment. ##### azure-openai Configure Azure OpenAI as an LLM provider in Port by storing your Azure credentials and registering your OpenAI deployment. - [Azure OpenAI setup](/port-ai/llm-providers-management/providers/azure-openai.md): Configure Azure OpenAI as an LLM provider in Port by storing your Azure credentials and registering your OpenAI deployment. ##### openai-compatible Configure any OpenAI-compatible endpoint as an LLM provider in Port, including LiteLLM proxies and custom gateways. - [OpenAI compatible setup](/port-ai/llm-providers-management/providers/openai-compatible.md): Configure any OpenAI-compatible endpoint as an LLM provider in Port, including LiteLLM proxies and custom gateways. ##### openai Configure OpenAI as an LLM provider in Port by storing your API key and registering your OpenAI account. - [OpenAI setup](/port-ai/llm-providers-management/providers/openai.md): Configure OpenAI as an LLM provider in Port by storing your API key and registering your OpenAI account. ##### overview Step-by-step setup guides for each LLM provider supported by Port, including OpenAI, Anthropic, Azure, AWS Bedrock, and Vertex AI. - [Provider guides overview](/port-ai/llm-providers-management/providers/overview.md): Step-by-step setup guides for each LLM provider supported by Port, including OpenAI, Anthropic, Azure, AWS Bedrock, and Vertex AI. ##### vertex-anthropic Configure Vertex Anthropic as an LLM provider in Port to use Claude models hosted on Google Vertex AI. - [Vertex Anthropic setup](/port-ai/llm-providers-management/providers/vertex-anthropic.md): Configure Vertex Anthropic as an LLM provider in Port to use Claude models hosted on Google Vertex AI. ##### vertex-gemini Configure Vertex Gemini as an LLM provider in Port using service account or Express Mode authentication. - [Vertex Gemini setup](/port-ai/llm-providers-management/providers/vertex-gemini.md): Configure Vertex Gemini as an LLM provider in Port using service account or Express Mode authentication. #### selecting-llm-provider Learn how to select specific LLM providers and models for Port AI requests, including default provider configuration and validation. - [Selecting LLM provider](/port-ai/llm-providers-management/selecting-llm-provider.md): Learn how to select specific LLM providers and models for Port AI requests, including default provider configuration and validation. #### setup-and-configuration Technical guide for setting up LLM providers in Port, including permissions, secrets management, validation, and troubleshooting. - [Setup & configuration](/port-ai/llm-providers-management/setup-and-configuration.md): Technical guide for setting up LLM providers in Port, including permissions, secrets management, validation, and troubleshooting. ### overview Use Port AI to query your software catalog in natural language, get contextual answers, and run actions through MCP tools. - [Port AI](/port-ai/overview.md): Use Port AI to query your software catalog in natural language, get contextual answers, and run actions through MCP tools. ### port-ai-assistant Use the Port AI Assistant for instant AI chat with ask, plan, and build modes, per-tool approval settings, and no configuration required. - [Port AI Assistant](/port-ai/port-ai-assistant.md): Use the Port AI Assistant for instant AI chat with ask, plan, and build modes, per-tool approval settings, and no configuration required. ### port-ai-plugin Install Port's Agent Skills and plugin to bring Port's context and MCP tools into your coding agent. - [Port AI plugin](/port-ai/port-ai-plugin.md): Install Port's Agent Skills and plugin to bring Port's context and MCP tools into your coding agent. ### security-and-data-controls Learn about Port AI's security practices, data privacy, RBAC enforcement, and controls for responsible AI usage. - [AI security and data controls](/port-ai/security-and-data-controls.md): Learn about Port AI's security practices, data privacy, RBAC enforcement, and controls for responsible AI usage. ### tools-and-approvals Control which tools Port AI can use, how they are approved, and which MCP connectors are attached to an API request. - [Tools and approvals](/port-ai/tools-and-approvals.md): Control which tools Port AI can use, how they are approved, and which MCP connectors are attached to an API request. ## agent-management ### AGENTS This is a hidden guide for AI agents to understand how to maintain and update the AI interfaces documentation in Port Docs. - [AGENTS.md - AI Documentation Maintenance Guide](/agent-management/AGENTS.md): This is a hidden guide for AI agents to understand how to maintain and update the AI interfaces documentation in Port Docs. ### ai-gateway What AI gateways do in the agentic SDLC, where they stop, and how Port governs agents, keys, and tools running through them. - [AI Gateway](/agent-management/ai-gateway.md): What AI gateways do in the agentic SDLC, where they stop, and how Port governs agents, keys, and tools running through them. ### ai-registry #### agent-registry Maintain a centralized agent registry in your Context Lake for AI agents deployed across cloud platforms such as AWS Bedrock and Azure AI Foundry. - [Agent registry](/agent-management/ai-registry/agent-registry.md): Maintain a centralized agent registry in your Context Lake for AI agents deployed across cloud platforms such as AWS Bedrock and Azure AI Foundry. #### managing-ai-primitives Short map of Port AI building blocks in the AI registry: skills, agents, MCP, context, and controls. - [Managing AI primitives](/agent-management/ai-registry/managing-ai-primitives.md): Short map of Port AI building blocks in the AI registry: skills, agents, MCP, context, and controls. #### mcp-connectors Set up MCP connectors as a Port gateway: approve servers, choose exposed tools, and let agents call them under RBAC and audit. - [MCP connectors](/agent-management/ai-registry/mcp-connectors.md): Set up MCP connectors as a Port gateway: approve servers, choose exposed tools, and let agents call them under RBAC and audit. ##### github-mcp-connector Set up the GitHub MCP connector to give Port AI access to GitHub repositories, issues, and pull requests through OAuth authentication. - [GitHub MCP connector](/agent-management/ai-registry/mcp-connectors/github-mcp-connector.md): Set up the GitHub MCP connector to give Port AI access to GitHub repositories, issues, and pull requests through OAuth authentication. ##### use-in-workflows Connect an MCP connector with organization-level OAuth so it can be called from an automated workflow AI node, and verify it end to end. - [Use in workflows](/agent-management/ai-registry/mcp-connectors/use-in-workflows.md): Connect an MCP connector with organization-level OAuth so it can be called from an automated workflow AI node, and verify it end to end. #### mcp-registry The MCP registry is an organizational catalog of MCP servers your teams are allowed to use. Platform teams define which servers are approved, developers discover what is available, and new servers enter through a governed request flow instead of ad hoc installs. - [MCP registry](/agent-management/ai-registry/mcp-registry.md): The MCP registry is an organizational catalog of MCP servers your teams are allowed to use. Platform teams define which servers are approved, developers discover what is available, and new servers enter through a governed request flow instead of ad hoc installs. #### prompts Prebuilt prompts for the Port MCP server that help AI agents query and act on your Port catalog. - [Prompts](/agent-management/ai-registry/prompts.md): Prebuilt prompts for the Port MCP server that help AI agents query and act on your Port catalog. #### skills ##### enrich-port-ai-with-skills Skills give Port AI domain-specific instructions at runtime. Instead of repeating the same prompts or hoping the model infers your process, you package expert workflows as skills that Port AI loads when a task matches. - [Enrich Port AI with skills](/agent-management/ai-registry/skills/enrich-port-ai-with-skills.md): Skills give Port AI domain-specific instructions at runtime. Instead of repeating the same prompts or hoping the model infers your process, you package expert workflows as skills that Port AI loads when a task matches. ##### overview Package Port AI skills with reusable instructions so agents load the right steps when they handle specialized tasks in your org. - [Skills](/agent-management/ai-registry/skills/overview.md): Package Port AI skills with reusable instructions so agents load the right steps when they handle specialized tasks in your org. ##### skills-registry A skills registry is a central library of reusable skills that everyone across the organization can publish and consume. Instead of each squad maintaining its own prompts and instructions, approved skills live in one catalog in Port, discovered and installed through the UI, Port MCP, or a CLI command. - [Skills registry](/agent-management/ai-registry/skills/skills-registry.md): A skills registry is a central library of reusable skills that everyone across the organization can publish and consume. Instead of each squad maintaining its own prompts and instructions, approved skills live in one catalog in Port, discovered and installed through the UI, Port MCP, or a CLI command. ##### skills-usage-analytics Analyze how teams adopt and use skills across Cursor and Claude by ingesting skill usage metrics into Port. - [Skills usage analytics](/agent-management/ai-registry/skills/skills-usage-analytics.md): Analyze how teams adopt and use skills across Cursor and Claude by ingesting skill usage metrics into Port. ### custom-agents #### build-an-ai-agent Build an AI agent in Port to assist your developers with finding information and completing tasks. - [Build agents](/agent-management/custom-agents/build-an-ai-agent.md): Build an AI agent in Port to assist your developers with finding information and completing tasks. #### interact-with-ai-agents Learn how to invoke and interact with Port AI agents programmatically through API calls, automations, and workflow integrations. - [Interact with agents](/agent-management/custom-agents/interact-with-ai-agents.md): Learn how to invoke and interact with Port AI agents programmatically through API calls, automations, and workflow integrations. #### overview Build Port custom agents for domain tasks that automate incidents, deployments, and monitoring as machine-to-machine operations. - [Port custom agents](/agent-management/custom-agents/overview.md): Build Port custom agents for domain tasks that automate incidents, deployments, and monitoring as machine-to-machine operations. ### external-agents #### claude-managed-agents Manage Claude agents and trigger sessions directly from Port workflows. - [Claude managed agents](/agent-management/external-agents/claude-managed-agents.md): Manage Claude agents and trigger sessions directly from Port workflows. #### cursor-cloud-agents Manage Cursor cloud agents and trigger runs directly from Port workflows. - [Cursor cloud agents](/agent-management/external-agents/cursor-cloud-agents.md): Manage Cursor cloud agents and trigger runs directly from Port workflows. #### overview Surface external AI agents in Context Lake, track ownership in a registry, and start sessions from Port workflows you already run. - [External agents](/agent-management/external-agents/overview.md): Surface external AI agents in Context Lake, track ownership in a registry, and start sessions from Port workflows you already run. ### overview Govern AI agents, skills, and MCP access in Port so you can discover what exists, control permissions, and operate agents safely. - [Agent management](/agent-management/overview.md): Govern AI agents, skills, and MCP access in Port so you can discover what exists, control permissions, and operate agents safely. ### port-mcp-server #### available-tools Reference for all tools exposed by the Port MCP Server, organized by developer and builder roles with detailed capabilities. - [Available tools](/agent-management/port-mcp-server/available-tools.md): Reference for all tools exposed by the Port MCP Server, organized by developer and builder roles with detailed capabilities. #### installation Install and configure the Port MCP server so AI agents and LLMs can interact with your Port developer portal using natural language. Covers OAuth and client-credentials authentication for local and CI use. - [Installation](/agent-management/port-mcp-server/installation.md): Install and configure the Port MCP server so AI agents and LLMs can interact with your Port developer portal using natural language. Covers OAuth and client-credentials authentication for local and CI use. #### overview Learn how the Port MCP server lets Claude, Cursor, and Copilot query your catalog and run governed actions from your IDE. - [Port MCP server](/agent-management/port-mcp-server/overview.md): Learn how the Port MCP server lets Claude, Cursor, and Copilot query your catalog and run governed actions from your IDE. #### token-based-authentication Connect to the Port MCP server from CI/CD pipelines and headless agents using OAuth client credentials, without interactive user consent. - [Machine authentication for the Port MCP server](/agent-management/port-mcp-server/token-based-authentication.md): Connect to the Port MCP server from CI/CD pipelines and headless agents using OAuth client credentials, without interactive user consent. ## interface-builder ### external-interfaces Build your own external interface for Port: a custom web UI, tool, or integration that signs users in through Port and calls the Port API within that person's own role and permissions, backed by Port's context lake and governance layer. - [External interfaces](/interface-builder/external-interfaces.md): Build your own external interface for Port: a custom web UI, tool, or integration that signs users in through Port and calls the Port API within that person's own role and permissions, backed by Port's context lake and governance layer. ### global-search Search across entities, actions, blueprints, and tech docs using Cmd+K. Configure which blueprints appear in global search results. - [Global search](/interface-builder/global-search.md): Search across entities, actions, blueprints, and tech docs using Cmd+K. Configure which blueprints appear in global search results. ### notifications #### slack-app Use Port's Slack app to interact with your developer portal and AI features directly from Slack, including notifications and catalog queries. - [Slack Application](/interface-builder/notifications/slack-app.md): Use Port's Slack app to interact with your developer portal and AI features directly from Slack, including notifications and catalog queries. ### overview Design Port catalog pages and dashboards where teams view live context and launch governed workflows from the same surface. - [Interface builder](/interface-builder/overview.md): Design Port catalog pages and dashboards where teams view live context and launch governed workflows from the same surface. ### port-interface #### customization Customize your portal with organization branding, logos, announcement banners, and accessibility settings like high contrast mode. - [Customization & accessibility](/interface-builder/port-interface/customization.md): Customize your portal with organization branding, logos, announcement banners, and accessibility settings like high contrast mode. #### dashboards ##### custom-widgets Add custom widgets including iframes for external content, markdown for formatted text, and action buttons to trigger self-service workflows. - [Custom widgets](/interface-builder/port-interface/dashboards/custom-widgets.md): Add custom widgets including iframes for external content, markdown for formatted text, and action buttons to trigger self-service workflows. ##### data-widgets Build data visualizations with number charts, pie charts, line charts, tables, and entity widgets. Supports aggregations, filtering, and conditional formatting. - [Data widgets](/interface-builder/port-interface/dashboards/data-widgets.md): Build data visualizations with number charts, pie charts, line charts, tables, and entity widgets. Supports aggregations, filtering, and conditional formatting. ##### overview Choose Port dashboard widgets for charts, tables, actions, and personal views to show catalog data on home and entity pages. - [Overview](/interface-builder/port-interface/dashboards/overview.md): Choose Port dashboard widgets for charts, tables, actions, and personal views to show catalog data on home and entity pages. ##### personal-widgets Add personal widgets to your home page showing your assigned entities, recently viewed items, and recently used actions. - [Personal widgets](/interface-builder/port-interface/dashboards/personal-widgets.md): Add personal widgets to your home page showing your assigned entities, recently viewed items, and recently used actions. #### page ##### catalog-page Customize Port catalog pages with table views, filters, grouping, sorting, and widgets. Configure initial filters, hide properties, save views, and control page visibility by team or role. - [Catalog page](/interface-builder/port-interface/page/catalog-page.md): Customize Port catalog pages with table views, filters, grouping, sorting, and widgets. Configure initial filters, hide properties, save views, and control page visibility by team or role. ##### dashboard-page Create dashboard pages with customizable widgets to display aggregated entity data, charts, and visualizations in your software catalog. Apply dashboard filters and save filter presets to switch between views. - [Dashboard page](/interface-builder/port-interface/page/dashboard-page.md): Create dashboard pages with customizable widgets to display aggregated entity data, charts, and visualizations in your software catalog. Apply dashboard filters and save filter presets to switch between views. ##### entity-page Customize entity pages with Overview, Related entities, Runs, and Audit log tabs. Add widgets and configure property visibility. - [Entity page](/interface-builder/port-interface/page/entity-page.md): Customize entity pages with Overview, Related entities, Runs, and Audit log tabs. Add widgets and configure property visibility. ##### folders Organize your software catalog by grouping pages into folders. Supports nesting up to 3 levels and drag-and-drop reordering. - [Folders](/interface-builder/port-interface/page/folders.md): Organize your software catalog by grouping pages into folders. Supports nesting up to 3 levels and drag-and-drop reordering. ##### page-permissions Control who can view or edit catalog and dashboard pages using role-based permissions for users and teams. - [Page permissions](/interface-builder/port-interface/page/page-permissions.md): Control who can view or edit catalog and dashboard pages using role-based permissions for users and teams. ##### private-pages Create personal catalog and dashboard pages in a dedicated Private sidebar section. Configure who can create private pages, change visibility between organization and private, and manage permissions on your own pages. - [Private pages](/interface-builder/port-interface/page/private-pages.md): Create personal catalog and dashboard pages in a dedicated Private sidebar section. Configure who can create private pages, change visibility between organization and private, and manage permissions on your own pages. #### plugins A plugin is a self-contained web UI, built from HTML, JavaScript, or a framework such as React, that you develop locally and upload to Port. Plugins power custom widgets that render inside dashboards and entity pages using the same catalog data and permissions as the rest of the platform. - [Plugins](/interface-builder/port-interface/plugins.md): A plugin is a self-contained web UI, built from HTML, JavaScript, or a framework such as React, that you develop locally and upload to Port. Plugins power custom widgets that render inside dashboards and entity pages using the same catalog data and permissions as the rest of the platform. ## governance ### governance-across-pillars #### overview Port's access controls live in each pillar, not one screen. See how catalog, workflow, AI agent, and interface permissions fit together. - [Governance across pillars](/governance/governance-across-pillars/overview.md): Port's access controls live in each pillar, not one screen. See how catalog, workflow, AI agent, and interface permissions fit together. #### scope-building-permissions-across-teams-and-users Scope who can build, run, and approve workflows and scorecards using team-based and dynamic RBAC, the same model that governs your catalog. - [Scope building permissions across teams and users](/governance/governance-across-pillars/scope-building-permissions-across-teams-and-users.md): Scope who can build, run, and approve workflows and scorecards using team-based and dynamic RBAC, the same model that governs your catalog. ### overview Scope what humans and agents can see and do in Port with scorecards, pillar permissions, and audit trails on every action. - [Governance layer](/governance/overview.md): Scope what humans and agents can see and do in Port with scorecards, pillar permissions, and audit trails on every action. ### standards-and-compliance #### concepts-and-structure Understand scorecard concepts including levels, rules, conditions, and filters. Learn how scorecards are structured and how entities are evaluated against defined standards. - [Concepts and structure](/governance/standards-and-compliance/concepts-and-structure.md): Understand scorecard concepts including levels, rules, conditions, and filters. Learn how scorecards are structured and how entities are evaluated against defined standards. #### examples ##### automation-use-cases Automate responses to scorecard rule changes including Slack/Teams notifications and GitHub issue creation. Includes automation configurations and GitHub workflow examples. - [Automation use-cases](/governance/standards-and-compliance/examples/automation-use-cases.md): Automate responses to scorecard rule changes including Slack/Teams notifications and GitHub issue creation. Includes automation configurations and GitHub workflow examples. ##### extend-data-model Extend scorecard blueprints with custom properties like SLA tracking and due dates. Build automated workflows for compliance monitoring, reminders, and exception handling. - [Extend the default scorecards](/governance/standards-and-compliance/examples/extend-data-model.md): Extend scorecard blueprints with custom properties like SLA tracking and due dates. Build automated workflows for compliance monitoring, reminders, and exception handling. ##### scorecard-use-cases Practical scorecard examples including ownership scorecards, relation existence checks, DORA metrics tracking, and agent dispatch guardrails. Ready-to-use JSON configurations you can implement immediately. - [Scorecard use-cases](/governance/standards-and-compliance/examples/scorecard-use-cases.md): Practical scorecard examples including ownership scorecards, relation existence checks, DORA metrics tracking, and agent dispatch guardrails. Ready-to-use JSON configurations you can implement immediately. #### manage-scorecard-groups Define engineering standards once and apply them across multiple blueprints with scorecard groups. Create, edit, and delete groups using the API or Terraform. - [Manage scorecard groups](/governance/standards-and-compliance/manage-scorecard-groups.md): Define engineering standards once and apply them across multiple blueprints with scorecard groups. Create, edit, and delete groups using the API or Terraform. #### manage-scorecards Create, edit, and delete Port scorecards using the UI, API, or Terraform. Includes step-by-step instructions and code examples for managing scorecards programmatically. - [Manage scorecards](/governance/standards-and-compliance/manage-scorecards.md): Create, edit, and delete Port scorecards using the UI, API, or Terraform. Includes step-by-step instructions and code examples for managing scorecards programmatically. #### manage-using-3rd-party-apps Integrate Port scorecards with Jira, GitHub, and Slack for automated issue tracking and notifications. Overview of available third-party app integrations. - [Manage using 3rd-party apps](/governance/standards-and-compliance/manage-using-3rd-party-apps.md): Integrate Port scorecards with Jira, GitHub, and Slack for automated issue tracking and notifications. Overview of available third-party app integrations. ##### github Automatically create and close GitHub issues for violated scorecard rules using the Port message sender. Sync scorecard compliance with GitHub issue tracking. - [Open/close GitHub issues](/governance/standards-and-compliance/manage-using-3rd-party-apps/github.md): Automatically create and close GitHub issues for violated scorecard rules using the Port message sender. Sync scorecard compliance with GitHub issue tracking. ##### jira Automatically create and close Jira issues for violated scorecard rules using the Port message sender. Sync scorecard compliance with Jira task tracking. - [Open/close JIRA issues](/governance/standards-and-compliance/manage-using-3rd-party-apps/jira.md): Automatically create and close Jira issues for violated scorecard rules using the Port message sender. Sync scorecard compliance with Jira task tracking. ##### slack Send Slack reports and reminders for scorecard compliance using the Port message sender GitHub action. Includes examples of scorecard reports and reminders. - [Send Slack messages](/governance/standards-and-compliance/manage-using-3rd-party-apps/slack.md): Send Slack reports and reminders for scorecard compliance using the Port message sender GitHub action. Includes examples of scorecard reports and reminders. #### overview Create scorecard rules and maturity levels that continuously measure services, teams, and resources against engineering standards. - [Standards and compliance](/governance/standards-and-compliance/overview.md): Create scorecard rules and maturity levels that continuously measure services, teams, and resources against engineering standards. ## platform-administration ### audit-log View a complete record of changes in your Port organization. Track entity, blueprint, action, automation, integration, and AI agent-triggered run modifications with filtering and export. - [Audit log](/platform-administration/audit-log.md): View a complete record of changes in your Port organization. Track entity, blueprint, action, automation, integration, and AI agent-triggered run modifications with filtering and export. ### multi-org-management #### manage-port-across-environments Learn how to manage Port resources across multiple environments using IaC (Terraform), Git-based resource definitions, or Port CLI. - [Manage Port across environments](/platform-administration/multi-org-management/manage-port-across-environments.md): Learn how to manage Port resources across multiple environments using IaC (Terraform), Git-based resource definitions, or Port CLI. #### multi-org-sso This page covers how to configure SSO when your Port company has multiple organizations, such as separate environments for development and production. It addresses common questions about IdP application registration, team synchronization, and RBAC across organizations. - [Multi-org SSO setup](/platform-administration/multi-org-management/multi-org-sso.md): This page covers how to configure SSO when your Port company has multiple organizations, such as separate environments for development and production. It addresses common questions about IdP application registration, team synchronization, and RBAC across organizations. #### multi-organization Manage multiple Port organizations with cross-organization access. Create organizations, switch between them, and share users. - [Multiple organization membership](/platform-administration/multi-org-management/multi-organization.md): Manage multiple Port organizations with cross-organization access. Create organizations, switch between them, and share users. ### overview Administer your Port organization: users and teams, SSO and SCIM, secrets, audit logs, usage, and multi-organization setup. - [Platform administration](/platform-administration/overview.md): Administer your Port organization: users and teams, SSO and SCIM, secrets, audit logs, usage, and multi-organization setup. ### plan-management Learn about Port's plan types. - [Plan management](/platform-administration/plan-management.md): Learn about Port's plan types. ### secrets-management #### port-secrets Manage Port API credentials (client ID, client secret) and organization secrets. Use secrets in integrations and automations with the {{ .secrets.SECRET_NAME }} template syntax. - [Credentials & secrets](/platform-administration/secrets-management/port-secrets.md): Manage Port API credentials (client ID, client secret) and organization secrets. Use secrets in integrations and automations with the {{ .secrets.SECRET_NAME }} template syntax. ### security Port security practices including SOC2 Type II, ISO 27001, GDPR compliance, data encryption, access controls, and infrastructure security. - [Security & compliance](/platform-administration/security.md): Port security practices including SOC2 Type II, ISO 27001, GDPR compliance, data encryption, access controls, and infrastructure security. ### sso-authentication #### manage-your-oauth-apps An OAuth app lets an external interface sign people into your Port organization, in their own role and permissions. This page covers creating, updating, and revoking OAuth apps. For the concept and how access works, see External interfaces. - [Manage your OAuth apps](/platform-administration/sso-authentication/manage-your-oauth-apps.md): An OAuth app lets an external interface sign people into your Port organization, in their own role and permissions. This page covers creating, updating, and revoking OAuth apps. For the concept and how access works, see External interfaces. #### SCIM SCIM provisioning is not self-serve. To enable it for your organization, contact Port support. - [SCIM](/platform-administration/sso-authentication/SCIM.md): SCIM provisioning is not self-serve. To enable it for your organization, contact Port support. #### self-serve-sso Set up SSO in Port, connect your identity provider, verify domains, and configure group filters and session settings as an admin. - [Manage your SSO connection](/platform-administration/sso-authentication/self-serve-sso.md): Set up SSO in Port, connect your identity provider, verify domains, and configure group filters and session settings as an admin. #### sso-providers Port offers several SSO integrations, allowing you to easily assign permissions and roles using your users and teams. - [SSO providers](/platform-administration/sso-authentication/sso-providers.md): Port offers several SSO integrations, allowing you to easily assign permissions and roles using your users and teams. ##### ldap Integrate any LDAP system with Port SSO - [LDAP](/platform-administration/sso-authentication/sso-providers/ldap.md): Integrate any LDAP system with Port SSO ##### oidc Integrate any SSO with Port using OIDC application - [OIDC](/platform-administration/sso-authentication/sso-providers/oidc.md): Integrate any SSO with Port using OIDC application - [Entra ID (AzureAD)](/platform-administration/sso-authentication/sso-providers/oidc/azure-ad.md): Integrate AzureAD with Port using OIDC - [Okta](/platform-administration/sso-authentication/sso-providers/oidc/okta.md): Integrate Okta with Port - [Onelogin](/platform-administration/sso-authentication/sso-providers/oidc/onelogin.md): Integrate Onelogin with Port ##### saml Integrate any SSO with Port using SAML application - [SAML](/platform-administration/sso-authentication/sso-providers/saml.md): Integrate any SSO with Port using SAML application - [Entra ID (AzureAD)](/platform-administration/sso-authentication/sso-providers/saml/azure-ad.md): Integrate Entra ID (AzureAD) with Port using SAML - [Google Workspace](/platform-administration/sso-authentication/sso-providers/saml/google-workspace.md): Integrate Google Workspace with Port using SAML - [JumpCloud](/platform-administration/sso-authentication/sso-providers/saml/jumpcloud.md): Integrate JumpCloud with Port using SAML - [Okta](/platform-administration/sso-authentication/sso-providers/saml/okta.md): Integrate Okta with Port using SAML ### usage-dashboard Track portal usage metrics including user activity, entity counts, action execution, integration usage, and AI interactions. Updated daily. - [Portal usage dashboard](/platform-administration/usage-dashboard.md): Track portal usage metrics including user activity, entity counts, action execution, integration usage, and AI interactions. Updated daily. ### users-and-teams #### manage-users-teams Manage users, teams, roles, and permissions in Port. Invite users, assign roles, create teams, and configure granular access controls. - [Manage users & teams](/platform-administration/users-and-teams/manage-users-teams.md): Manage users, teams, roles, and permissions in Port. Invite users, assign roles, create teams, and configure granular access controls. #### user-membership-reference A complete reference for how users are added to organizations in Port — SSO, SCIM, invitations, member statuses, and edge cases. - [User membership reference](/platform-administration/users-and-teams/user-membership-reference.md): A complete reference for how users are added to organizations in Port — SSO, SCIM, invitations, member statuses, and edge cases. #### view-as-different-user Test permissions by viewing the portal as a different user. Validate dynamic filters, role-based access, and page visibility without switching accounts. - [View as a different user](/platform-administration/users-and-teams/view-as-different-user.md): Test permissions by viewing the portal as a different user. Validate dynamic filters, role-based access, and page visibility without switching accounts. ## Guides Step-by-step tutorials and walkthroughs for specific Port use cases. These guides show you how to implement common scenarios with Port. For API reference and concept documentation, see the reference sections in /context-lake/ and /api-reference/. - [Guides](/guides.md): Step-by-step tutorials and walkthroughs for specific Port use cases. These guides show you how to implement common scenarios with Port. For API reference and concept documentation, see the reference sections in /context-lake/ and /api-reference/. ### All guides #### add-tags-to-aws-resources Enable agents and developers to add tags to AWS S3 buckets and ECR repositories through governed Port workflows. - [Add tags to AWS resources](/guides/all/add-tags-to-aws-resources.md): Enable agents and developers to add tags to AWS S3 buckets and ECR repositories through governed Port workflows. #### ai-powered-github-workflow-documentation Enable agents and developers to automatically generate and maintain human-readable documentation for GitHub Actions workflows using Port's AI agents and automation system. - [Auto-generate GitHub workflow documentation](/guides/all/ai-powered-github-workflow-documentation.md): Enable agents and developers to automatically generate and maintain human-readable documentation for GitHub Actions workflows using Port's AI agents and automation system. #### ai-powered-incident-management Enable agents and developers to implement an AI-powered incident management lifecycle in Port, from triage and orchestration to incident updates, RCA, and postmortems. - [AI-powered incident management](/guides/all/ai-powered-incident-management.md): Enable agents and developers to implement an AI-powered incident management lifecycle in Port, from triage and orchestration to incident updates, RCA, and postmortems. #### apply-labels-to-github-pr-based-on-sonar-scans Enable agents and developers to automatically label your GitHub pull request based on SonarQube scan reports. - [Auto-label your GitHub PRs with Sonar Scans](/guides/all/apply-labels-to-github-pr-based-on-sonar-scans.md): Enable agents and developers to automatically label your GitHub pull request based on SonarQube scan reports. #### approval-workflow-for-gitlab-deployment Enable agents and developers to govern GitLab deployments with Port workflows and ServiceNow approvals. - [Approval workflows for service deployment](/guides/all/approval-workflow-for-gitlab-deployment.md): Enable agents and developers to govern GitLab deployments with Port workflows and ServiceNow approvals. #### assign-pr-reviewers-with-claude Enable agents and developers to automatically assess pull request risk, assign reviewers, comment, and auto-approve low-risk changes using a Claude managed agent triggered from a Port workflow. - [Assign PR reviewers with a Claude managed agent](/guides/all/assign-pr-reviewers-with-claude.md): Enable agents and developers to automatically assess pull request risk, assign reviewers, comment, and auto-approve low-risk changes using a Claude managed agent triggered from a Port workflow. #### auto-assign-bugs-to-owners Enable agents and developers to automatically assign Jira bugs to owning teams and services with Port automations and AI. - [Auto-assign bugs to owners with AI](/guides/all/auto-assign-bugs-to-owners.md): Enable agents and developers to automatically assign Jira bugs to owning teams and services with Port automations and AI. #### auto-enhance-pr-descriptions-with-ai Enable agents and developers to automatically enhance PR descriptions with AI-generated, template-compliant content using a Port workflow, an AI agent, and coding assistants. - [Auto-enhance PR descriptions with AI](/guides/all/auto-enhance-pr-descriptions-with-ai.md): Enable agents and developers to automatically enhance PR descriptions with AI-generated, template-compliant content using a Port workflow, an AI agent, and coding assistants. #### auto-remediate-insecure-cloud-resources-with-ai Enable agents and developers to automatically detect and remediate insecure cloud resources using Port workflows and Claude Code to generate infrastructure-as-code patches. - [Auto-remediate insecure cloud resources with AI](/guides/all/auto-remediate-insecure-cloud-resources-with-ai.md): Enable agents and developers to automatically detect and remediate insecure cloud resources using Port workflows and Claude Code to generate infrastructure-as-code patches. #### automate-slack-alert-for-overdue-prs Enable agents and developers to keep pull request reviews moving with automated Slack alerts for overdue pull requests. - [Automate Slack Alert for Overdue PRs](/guides/all/automate-slack-alert-for-overdue-prs.md): Enable agents and developers to keep pull request reviews moving with automated Slack alerts for overdue pull requests. #### automate-user-role-update Enable agents and developers to maintain governed Port access by assigning the Admin role when users join an administrators team. - [Automate admin role assignment](/guides/all/automate-user-role-update.md): Enable agents and developers to maintain governed Port access by assigning the Admin role when users join an administrators team. #### automatically-approve-action-using-automation Enable agents and developers to govern routine requests with automated approvals for Port self-service actions. - [Automate self-service action approvals](/guides/all/automatically-approve-action-using-automation.md): Enable agents and developers to govern routine requests with automated approvals for Port self-service actions. #### automatically-resolve-tickets Enable agents and developers to resolve Jira tickets with coding agents through governed Port workflows or an n8n workflow enriched by Port context. - [Automatically resolve tickets with Port](/guides/all/automatically-resolve-tickets.md): Enable agents and developers to resolve Jira tickets with coding agents through governed Port workflows or an n8n workflow enriched by Port context. #### automatically-set-relations-between-entities-with-automation Enable agents and developers to link service entities to owning teams through Port automations without custom integration code. - [Automatically set relations between entities with automation](/guides/all/automatically-set-relations-between-entities-with-automation.md): Enable agents and developers to link service entities to owning teams through Port automations without custom integration code. #### avoid-duplicate-skills-in-your-org-registry Catch duplicate skills before they reach the registry: flag a similar skill while it's being published, score uniqueness during certification, and surface both on your dashboards. - [Avoid duplicate skills in your org registry](/guides/all/avoid-duplicate-skills-in-your-org-registry.md): Catch duplicate skills before they reach the registry: flag a similar skill while it's being published, score uniqueness during certification, and surface both on your dashboards. #### azure-resource-tagging-initiative Enable agents and developers to track and enforce Azure resource tagging compliance using Port scorecards, dashboards, and self-service actions. - [Track Azure resource tagging compliance](/guides/all/azure-resource-tagging-initiative.md): Enable agents and developers to track and enforce Azure resource tagging compliance using Port scorecards, dashboards, and self-service actions. #### build-ai-agent-registry Enable agents and developers to build a centralized AI Agent Registry in Port to discover, govern, and manage AI agents deployed across AWS Bedrock and Azure AI Foundry. - [Build an AI Agent Registry](/guides/all/build-ai-agent-registry.md): Enable agents and developers to build a centralized AI Agent Registry in Port to discover, govern, and manage AI agents deployed across AWS Bedrock and Azure AI Foundry. #### build-github-dashboards-in-port Enable agents and developers to build GitHub dashboards in Port for dependency security, identity and access, repository activity, and engineering metrics. - [Build GitHub dashboards in Port](/guides/all/build-github-dashboards-in-port.md): Enable agents and developers to build GitHub dashboards in Port for dependency security, identity and access, repository activity, and engineering metrics. #### build-port-with-mcp Use the Port MCP server with your assistant to create blueprints, actions, automations, and scorecards from described outcomes. - [Build and configure Port using the MCP server](/guides/all/build-port-with-mcp.md): Use the Port MCP server with your assistant to create blueprints, actions, automations, and scorecards from described outcomes. #### build-port-workflows-with-mcp Use AI and Port's MCP server to build Port workflows through natural language conversations, designing multi-step node graphs without hand-writing workflow JSON. - [Build workflows with AI](/guides/all/build-port-workflows-with-mcp.md): Use AI and Port's MCP server to build Port workflows through natural language conversations, designing multi-step node graphs without hand-writing workflow JSON. #### bundle-skills-into-plugins-in-your-org-registry Group related skills into installable plugins: ingest the plugins already in your GitHub repo, get an AI-driven suggestion when a certified skill belongs in one, and apply it through a self-service workflow. - [Bundle skills into plugins](/guides/all/bundle-skills-into-plugins-in-your-org-registry.md): Group related skills into installable plugins: ingest the plugins already in your GitHub repo, get an AI-driven suggestion when a certified skill belongs in one, and apply it through a self-service workflow. #### calculate-blast-radius-with-ai Enable agents and developers to calculate deployment blast radius and production risk using AI, service dependencies, and Port workflows. - [Calculate blast radius with AI](/guides/all/calculate-blast-radius-with-ai.md): Enable agents and developers to calculate deployment blast radius and production risk using AI, service dependencies, and Port workflows. #### certify-skills-to-meet-industry-and-org-standards Certify skills against deterministic and non-deterministic standards, starting with description quality, the one property that decides whether an agent ever calls a skill at all. - [Certify skills to meet industry and org standards](/guides/all/certify-skills-to-meet-industry-and-org-standards.md): Certify skills against deterministic and non-deterministic standards, starting with description quality, the one property that decides whether an agent ever calls a skill at all. #### connect-github-codeowners-with-service-team-and-user Enable agents and developers to connect GitHub CODEOWNERS with Port repositories, teams, and users for governed ownership context. - [Connect GitHub CODEOWNERS with Repository, Team & User](/guides/all/connect-github-codeowners-with-service-team-and-user.md): Enable agents and developers to connect GitHub CODEOWNERS with Port repositories, teams, and users for governed ownership context. #### connect-github-pr-with-jira-issue Enable agents and developers to trace delivery work by connecting GitHub pull requests to Jira issues in Port. - [Connect GitHub Pull Request with Jira Issue](/guides/all/connect-github-pr-with-jira-issue.md): Enable agents and developers to trace delivery work by connecting GitHub pull requests to Jira issues in Port. #### copy-pipeline-template-to-target-repo Enable agents and developers to copy pipeline template from one repository to another using Port Actions. - [Copy Pipeline Template to Target Repo](/guides/all/copy-pipeline-template-to-target-repo.md): Enable agents and developers to copy pipeline template from one repository to another using Port Actions. #### create-an-ec2-instance Enable agents and developers to create an EC2 instance in Port using GitHub Actions or GitLab CI. - [Create an AWS EC2 instance](/guides/all/create-an-ec2-instance.md): Enable agents and developers to create an EC2 instance in Port using GitHub Actions or GitLab CI. #### create-cloud-resource-using-iac Enable agents and developers to create cloud resources using Infrastructure as Code (IaC) in Port for efficient and automated deployments. - [Create cloud resources using IaC](/guides/all/create-cloud-resource-using-iac.md): Enable agents and developers to create cloud resources using Infrastructure as Code (IaC) in Port for efficient and automated deployments. #### create-engineering-intelligence-pulse Enable agents and developers to run a weekly Engineering Intelligence assessment that ranks the engineering improvements worth making, records each one with its evidence, and publishes a brief to Slack. - [Create the Engineering Intelligence Pulse](/guides/all/create-engineering-intelligence-pulse.md): Enable agents and developers to run a weekly Engineering Intelligence assessment that ranks the engineering improvements worth making, records each one with its evidence, and publishes a brief to Slack. #### create-github-copilot-ai-usage-dashboard-custom-plugin Enable agents and developers to track GitHub Copilot adoption, acceptance rates, and AI-generated insights with a Port custom plugin. - [Measure GitHub Copilot adoption and usage with plugins](/guides/all/create-github-copilot-ai-usage-dashboard-custom-plugin.md): Enable agents and developers to track GitHub Copilot adoption, acceptance rates, and AI-generated insights with a Port custom plugin. #### create-incident-io-incident Enable agents and developers to initiate incident.io response through governed Port self-service actions. - [Create an incident.io Incident](/guides/all/create-incident-io-incident.md): Enable agents and developers to initiate incident.io response through governed Port self-service actions. #### create-jira-issue-from-datadog-alert Enable agents and developers to create Jira issues from Datadog and other alerts through governed Port workflows and Jira webhooks. - [Create a Jira issue from a monitoring alert](/guides/all/create-jira-issue-from-datadog-alert.md): Enable agents and developers to create Jira issues from Datadog and other alerts through governed Port workflows and Jira webhooks. #### create-jira-issue-gitlab Enable agents and developers to create Jira issues through a governed Port self-service action backed by GitLab CI. - [Create a Jira issue using GitLab CI](/guides/all/create-jira-issue-gitlab.md): Enable agents and developers to create Jira issues through a governed Port self-service action backed by GitLab CI. #### create-new-aws-account-gitlab Enable agents and developers to provision AWS accounts through a governed Port workflow that validates the request with AI and provisions it with GitLab CI/CD and Terraform. - [Automate AWS account creation with GitLab](/guides/all/create-new-aws-account-gitlab.md): Enable agents and developers to provision AWS accounts through a governed Port workflow that validates the request with AI and provisions it with GitLab CI/CD and Terraform. #### create-organizational-hierarchy-views Enable agents and developers to model and visualize hierarchy tiers in your organization. - [Create organizational hierarchy views](/guides/all/create-organizational-hierarchy-views.md): Enable agents and developers to model and visualize hierarchy tiers in your organization. #### create-roi-dashboard Enable agents and developers to create a dashboard that tracks the ROI of actions in Port. - [Create an actions ROI dashboard](/guides/all/create-roi-dashboard.md): Enable agents and developers to create a dashboard that tracks the ROI of actions in Port. #### create-slack-channel-for-reported-incident-with-port-workflows Learn how to automatically create a Slack channel and GitHub issue when a PagerDuty incident is reported, using Port Workflows for real-time team communication and incident documentation. - [Auto-create Slack channel & GitHub issue for PagerDuty incidents](/guides/all/create-slack-channel-for-reported-incident-with-port-workflows.md): Learn how to automatically create a Slack channel and GitHub issue when a PagerDuty incident is reported, using Port Workflows for real-time team communication and incident documentation. #### create-slack-channel-for-reported-incident Enable agents and developers to coordinate PagerDuty response by creating a Slack channel and GitHub issue automatically through Port. - [Auto-create Slack channel & GitHub issue for PagerDuty incidents](/guides/all/create-slack-channel-for-reported-incident.md): Enable agents and developers to coordinate PagerDuty response by creating a Slack channel and GitHub issue automatically through Port. #### create-survey-intelligence Enable agents and developers to deploy the Survey Intelligence plugins (Survey Builder, Survey Forms, and Survey Analytics) to run engineering surveys and turn responses into insights. - [Create Survey Intelligence](/guides/all/create-survey-intelligence.md): Enable agents and developers to deploy the Survey Intelligence plugins (Survey Builder, Survey Forms, and Survey Analytics) to run engineering surveys and turn responses into insights. #### deploy-azure-resource-with-terraform Enable agents and developers to deploy Azure storage accounts with Terraform from Port using GitHub Actions, Azure DevOps, or Jenkins. - [Deploy Azure resource with Terraform](/guides/all/deploy-azure-resource-with-terraform.md): Enable agents and developers to deploy Azure storage accounts with Terraform from Port using GitHub Actions, Azure DevOps, or Jenkins. #### deploy-s3-bucket-crossplane Enable agents and developers to provision S3 buckets through governed Port workflows backed by Crossplane. - [Deploy S3 Bucket using Crossplane](/guides/all/deploy-s3-bucket-crossplane.md): Enable agents and developers to provision S3 buckets through governed Port workflows backed by Crossplane. #### enrich-tasks-with-ai Enable agents and developers to enrich tasks with AI-powered context using Port workflows, providing developers with automated insights, related issues, and potential collaborators. - [Enrich tasks with AI-powered context](/guides/all/enrich-tasks-with-ai.md): Enable agents and developers to enrich tasks with AI-powered context using Port workflows, providing developers with automated insights, related issues, and potential collaborators. #### ensure-production-readiness Enable agents and developers to ensure production readiness in Port, optimizing your deployments for reliability and performance. - [Ensure production readiness](/guides/all/ensure-production-readiness.md): Enable agents and developers to ensure production readiness in Port, optimizing your deployments for reliability and performance. #### find-internal-documentation-with-ai Enable agents and developers to find internal documentation with AI. - [Find internal documentation with AI](/guides/all/find-internal-documentation-with-ai.md): Enable agents and developers to find internal documentation with AI. #### generate-zendesk-ticket-summaries-with-ai Summarize Zendesk tickets using workflows that fetches ticket comments and side conversations, then uses Port AI to produce a structured summary. - [Summarize Zendesk tickets with AI](/guides/all/generate-zendesk-ticket-summaries-with-ai.md): Summarize Zendesk tickets using workflows that fetches ticket comments and side conversations, then uses Port AI to produce a structured summary. #### heal-unhealthy-k8s-pods Build a single Port workflow that detects unhealthy Kubernetes workloads, diagnoses them with Port AI, and applies targeted fixes automatically. - [Auto-heal unhealthy Kubernetes pods with Port workflows](/guides/all/heal-unhealthy-k8s-pods.md): Build a single Port workflow that detects unhealthy Kubernetes workloads, diagnoses them with Port AI, and applies targeted fixes automatically. #### iam-permissions-guide Enable agents and developers to request time-bound AWS IAM permissions through governed Port workflows and automated revocation. - [IAM permission management](/guides/all/iam-permissions-guide.md): Enable agents and developers to request time-bound AWS IAM permissions through governed Port workflows and automated revocation. #### implement-rbac-for-ai-agents-with-n8n-and-port Enable agents and developers to enforce role-based tool access for AI agents using Port and n8n. - [RBAC for AI agents with n8n and Port](/guides/all/implement-rbac-for-ai-agents-with-n8n-and-port.md): Enable agents and developers to enforce role-based tool access for AI agents using Port and n8n. #### incident-blueprint Enable agents and developers to implement the incident blueprint pattern for self-healing incident workflows. - [Implement the incident blueprint pattern](/guides/all/incident-blueprint.md): Enable agents and developers to implement the incident blueprint pattern for self-healing incident workflows. #### ingest-claude-code-security-reviews Enable agents and developers to ingest Claude Code security review findings and visualize them in Port dashboards. - [Ingest Claude Code security reviews](/guides/all/ingest-claude-code-security-reviews.md): Enable agents and developers to ingest Claude Code security review findings and visualize them in Port dashboards. #### ingest-cloud-resources-using-apm-integrations Enable agents and developers to ingest cloud resources monitored by Datadog, Dynatrace, or New Relic into Port. - [Ingest cloud resources using APM integrations](/guides/all/ingest-cloud-resources-using-apm-integrations.md): Enable agents and developers to ingest cloud resources monitored by Datadog, Dynatrace, or New Relic into Port. #### ingest-data-via-airbyte-and-s3 Enable agents and developers to ingest HiBob or Okta data into Port using Airbyte, Amazon S3, and a custom webhook integration. - [Ingest data via Airbyte and S3](/guides/all/ingest-data-via-airbyte-and-s3.md): Enable agents and developers to ingest HiBob or Okta data into Port using Airbyte, Amazon S3, and a custom webhook integration. #### ingest-dotnet-framework-versions Enable agents and developers to ingest .NET framework versions from .csproj files and track them across your repositories using Port. - [Ingest .NET framework versions from repositories](/guides/all/ingest-dotnet-framework-versions.md): Enable agents and developers to ingest .NET framework versions from .csproj files and track them across your repositories using Port. #### ingest-gitlab-vulnerabilities-into-your-context-lake Enable agents and developers to centralize GitLab SAST, DAST, dependency, and container findings by team and service in Port's context lake. - [Ingest GitLab security vulnerabilities into your context lake](/guides/all/ingest-gitlab-vulnerabilities-into-your-context-lake.md): Enable agents and developers to centralize GitLab SAST, DAST, dependency, and container findings by team and service in Port's context lake. #### ingest-linearb-metrics-into-your-context-lake Enable agents and developers to consolidate LinearB engineering metrics into Port's Engineering Intelligence platform, leveraging Port's native analytics alongside imported data for a unified view of engineering performance. - [Ingest LinearB metrics into your context lake](/guides/all/ingest-linearb-metrics-into-your-context-lake.md): Enable agents and developers to consolidate LinearB engineering metrics into Port's Engineering Intelligence platform, leveraging Port's native analytics alongside imported data for a unified view of engineering performance. #### ingest-prompts-skills-from-github-using-gitops Enable agents and developers to ingest AI prompts and skills from GitHub using GitOps by mapping repo files to Port blueprints. - [Ingest prompts and skills from GitHub using GitOps](/guides/all/ingest-prompts-skills-from-github-using-gitops.md): Enable agents and developers to ingest AI prompts and skills from GitHub using GitOps by mapping repo files to Port blueprints. #### ingest-repository-files-into-your-context-lake Enable agents and developers to parse package manifests, API specifications, SBOMs, SARIF files, and security scan results from repositories into Port. - [Ingest repository files into your context lake](/guides/all/ingest-repository-files-into-your-context-lake.md): Enable agents and developers to parse package manifests, API specifications, SBOMs, SARIF files, and security scan results from repositories into Port. #### ingest-slack-data-via-airbyte-s3-and-webhook Enable agents and developers to ingest Slack channels, users, and memberships into Port using Airbyte or Fivetran, S3, and a webhook integration. - [Ingest Slack data into Port](/guides/all/ingest-slack-data-via-airbyte-s3-and-webhook.md): Enable agents and developers to ingest Slack channels, users, and memberships into Port using Airbyte or Fivetran, S3, and a webhook integration. #### ingest-swarmia-metrics-into-your-context-lake Enable agents and developers to consolidate Swarmia DORA and pull request metrics into Port's Engineering Intelligence platform, leveraging Port's native analytics alongside imported data for a unified view of engineering performance. - [Ingest Swarmia metrics into your context lake](/guides/all/ingest-swarmia-metrics-into-your-context-lake.md): Enable agents and developers to consolidate Swarmia DORA and pull request metrics into Port's Engineering Intelligence platform, leveraging Port's native analytics alongside imported data for a unified view of engineering performance. #### ingest-vulnerability-alerts-from-orca-security-using-a-custom-webhook-integration Enable agents and developers to investigate Orca Security vulnerabilities by ingesting historical and real-time alerts into Port. - [Ingest vulnerability alerts from Orca Security](/guides/all/ingest-vulnerability-alerts-from-orca-security-using-a-custom-webhook-integration.md): Enable agents and developers to investigate Orca Security vulnerabilities by ingesting historical and real-time alerts into Port. #### interact-with-pagerduty-incidents Learn how to create, trigger, reassign, and resolve PagerDuty incidents from Port using a single native workflow, and keep your catalog in sync. - [Interact with PagerDuty incidents](/guides/all/interact-with-pagerduty-incidents.md): Learn how to create, trigger, reassign, and resolve PagerDuty incidents from Port using a single native workflow, and keep your catalog in sync. #### interact-with-servicenow Enable agents and developers to create, update, trigger, and delete ServiceNow records and incidents using Port workflows. - [Interact with ServiceNow records & incidents](/guides/all/interact-with-servicenow.md): Enable agents and developers to create, update, trigger, and delete ServiceNow records and incidents using Port workflows. #### keep-azure-catalog-upto-date-with-incremental-syncing Enable agents and developers to keep your Azure catalog up to date by syncing only changed resources into Port using GitHub Actions or a local script. - [Keep your Azure catalog up to date with incremental syncing](/guides/all/keep-azure-catalog-upto-date-with-incremental-syncing.md): Enable agents and developers to keep your Azure catalog up to date by syncing only changed resources into Port using GitHub Actions or a local script. #### let-developers-enrich-services-using-gitops Enable agents and developers to enrich service context through Git-managed repository metadata ingested into Port. - [Enrich services using GitOps](/guides/all/let-developers-enrich-services-using-gitops.md): Enable agents and developers to enrich service context through Git-managed repository metadata ingested into Port. #### lock-and-unlock-services-in-port Enable agents and developers to govern deployments with Port service locks and automated GitHub workflow checks. - [Lock and unlock services](/guides/all/lock-and-unlock-services-in-port.md): Enable agents and developers to govern deployments with Port service locks and automated GitHub workflow checks. #### manage-and-surface-technical-docs Enable agents and developers to manage and surface technical documentation in Port by ingesting markdown from a Git source, displaying it on service or repository pages, and adding the TechDocs plugin. - [Manage and surface technical documentation in Port](/guides/all/manage-and-surface-technical-docs.md): Enable agents and developers to manage and surface technical documentation in Port by ingesting markdown from a Git source, displaying it on service or repository pages, and adding the TechDocs plugin. #### manage-and-visualize-cloud-resources Enable agents and developers to manage and visualize AWS, Azure, and GCP cloud resources in Port using integrations, self-service actions, and dashboards. - [Manage and visualize cloud resources](/guides/all/manage-and-visualize-cloud-resources.md): Enable agents and developers to manage and visualize AWS, Azure, and GCP cloud resources in Port using integrations, self-service actions, and dashboards. #### manage-and-visualize-jira-issues Enable agents and developers to monitor and manage Jira issues using dashboards, labels, and self-service actions in Port. - [Manage and visualize your Jira issues](/guides/all/manage-and-visualize-jira-issues.md): Enable agents and developers to monitor and manage Jira issues using dashboards, labels, and self-service actions in Port. #### manage-and-visualize-pagerduty-incidents Enable agents and developers to manage your PagerDuty incidents with self-service actions and monitor them using dashboards in Port. - [Manage and visualize your PagerDuty incidents](/guides/all/manage-and-visualize-pagerduty-incidents.md): Enable agents and developers to manage your PagerDuty incidents with self-service actions and monitor them using dashboards in Port. #### manage-and-visualize-your-launchdarkly-feature-flags Enable agents and developers to manage and visualize your LaunchDarkly feature flags using dashboards and self-service actions in Port. - [Manage and visualize your LaunchDarkly feature flags](/guides/all/manage-and-visualize-your-launchdarkly-feature-flags.md): Enable agents and developers to manage and visualize your LaunchDarkly feature flags using dashboards and self-service actions in Port. #### manage-argocd-applications Enable agents and developers to sync, restart, and self-heal Argo CD applications from Port using governed workflows, Port AI diagnosis, and GitHub Actions backends. - [Manage Argo CD applications](/guides/all/manage-argocd-applications.md): Enable agents and developers to sync, restart, and self-heal Argo CD applications from Port using governed workflows, Port AI diagnosis, and GitHub Actions backends. #### manage-clusters Enable agents and developers to provision and manage Kubernetes clusters through governed Port workflows backed by Crossplane and Argo CD. - [Create and manage Kubernetes clusters](/guides/all/manage-clusters.md): Enable agents and developers to provision and manage Kubernetes clusters through governed Port workflows backed by Crossplane and Argo CD. #### manage-developer-environments Enable agents and developers to create, extend, and tear down developer environments through governed Port workflows backed by GitHub Actions. - [Manage developer environments](/guides/all/manage-developer-environments.md): Enable agents and developers to create, extend, and tear down developer environments through governed Port workflows backed by GitHub Actions. #### manage-firehydrant-incidents Enable agents and developers to manage FireHydrant incidents through governed Port actions while keeping incident context synchronized. - [Manage FireHydrant incidents](/guides/all/manage-firehydrant-incidents.md): Enable agents and developers to manage FireHydrant incidents through governed Port actions while keeping incident context synchronized. #### manage-humanitec-applications Enable agents and developers to ingest Humanitec resources into Port and manage Humanitec applications with self-service actions. - [Manage Humanitec applications](/guides/all/manage-humanitec-applications.md): Enable agents and developers to ingest Humanitec resources into Port and manage Humanitec applications with self-service actions. #### manage-incident-lifecycle Enable agents and developers to create governed self-service actions to acknowledge, escalate, and resolve PagerDuty incidents from Port. - [Manage incident lifecycle](/guides/all/manage-incident-lifecycle.md): Enable agents and developers to create governed self-service actions to acknowledge, escalate, and resolve PagerDuty incidents from Port. #### manage-kubernetes-namespaces Enable agents and developers to create and manage Kubernetes namespaces through Port with automated workflows, approval processes, and chained self-service actions. - [Manage Kubernetes namespaces](/guides/all/manage-kubernetes-namespaces.md): Enable agents and developers to create and manage Kubernetes namespaces through Port with automated workflows, approval processes, and chained self-service actions. #### manage-mcp-registry Enable agents and developers to build a governed MCP server registry in Port using workflows, MCP connectors, a dashboard, and an AI assistant, so teams can request, review, and discover approved MCP servers. - [Manage MCP server registry](/guides/all/manage-mcp-registry.md): Enable agents and developers to build a governed MCP server registry in Port using workflows, MCP connectors, a dashboard, and an AI assistant, so teams can request, review, and discover approved MCP servers. #### manage-pagerduty-on-call-schedules Enable agents and developers to manage and track PagerDuty on-call schedules across teams and services with dashboards and Port workflows. - [Manage PagerDuty on-call schedules](/guides/all/manage-pagerduty-on-call-schedules.md): Enable agents and developers to manage and track PagerDuty on-call schedules across teams and services with dashboards and Port workflows. #### manage-pull-requests Enable agents and developers to manage GitHub pull requests from Port using a workflow that dispatches a GitHub Actions pipeline to close, merge, or approve PRs. - [Manage GitHub pull requests](/guides/all/manage-pull-requests.md): Enable agents and developers to manage GitHub pull requests from Port using a workflow that dispatches a GitHub Actions pipeline to close, merge, or approve PRs. #### manage-your-eks-clusters Enable agents and developers to create, request, deploy to, tag, and delete Amazon EKS clusters using Port workflows. - [Manage EKS clusters](/guides/all/manage-your-eks-clusters.md): Enable agents and developers to create, request, deploy to, tag, and delete Amazon EKS clusters using Port workflows. #### manage-your-kubernetes-deployment Restart Kubernetes deployments, change replica counts, and delete pods with a self-service Port workflow that dispatches GitHub Actions through the GitHub Ocean integration. - [Manage your Kubernetes deployments](/guides/all/manage-your-kubernetes-deployment.md): Restart Kubernetes deployments, change replica counts, and delete pods with a self-service Port workflow that dispatches GitHub Actions through the GitHub Ocean integration. #### map-external-users-and-teams-to-port-accounts Enable agents and developers to link external user and team identities to Port accounts for accurate ownership, access, and workflow routing. - [Map external tool users and teams to Port accounts](/guides/all/map-external-users-and-teams-to-port-accounts.md): Enable agents and developers to link external user and team identities to Port accounts for accurate ownership, access, and workflow routing. #### map-prs-to-services-in-monorepo Enable agents and developers to automatically map pull requests to services in a monorepo using file path analysis. - [Map PRs to services in a monorepo](/guides/all/map-prs-to-services-in-monorepo.md): Enable agents and developers to automatically map pull requests to services in a monorepo using file path analysis. #### measure-ai-feedback Enable agents and developers to collect and analyze feedback on AI tools using Port actions, automations, and AI agents to measure effectiveness and guide adoption decisions. - [Measure AI feedback](/guides/all/measure-ai-feedback.md): Enable agents and developers to collect and analyze feedback on AI tools using Port actions, automations, and AI agents to measure effectiveness and guide adoption decisions. #### measure-pipeline-reliability Enable agents and developers to measure CI/CD pipeline reliability at service and team level, including failure rates, failure trends, and failing pipeline identification, using Port's integrations and dashboards. - [Measure pipeline reliability](/guides/all/measure-pipeline-reliability.md): Enable agents and developers to measure CI/CD pipeline reliability at service and team level, including failure rates, failure trends, and failing pipeline identification, using Port's integrations and dashboards. #### measure-pr-delivery-metrics Enable agents and developers to measure PR and MR delivery metrics at service and team level, including cycle time, throughput, stale PRs/MRs, and reviewer coverage, using Port's GitHub Ocean or GitLab integration and dashboards. - [Measure PR delivery metrics](/guides/all/measure-pr-delivery-metrics.md): Enable agents and developers to measure PR and MR delivery metrics at service and team level, including cycle time, throughput, stale PRs/MRs, and reviewer coverage, using Port's GitHub Ocean or GitLab integration and dashboards. #### measure-pull-request-standards Enable agents and developers to implement working agreements and measure pull request standards using Port scorecards and aggregation properties, tracking team performance based on PR metrics like review time, approval count, and merge latency. - [Measuring pull request standards](/guides/all/measure-pull-request-standards.md): Enable agents and developers to implement working agreements and measure pull request standards using Port scorecards and aggregation properties, tracking team performance based on PR metrics like review time, approval count, and merge latency. #### measure-reliability-and-stability Enable agents and developers to measure reliability and stability of your delivery pipeline by tracking workflow failure rates and PRs blocked by failing CI/CD using Port's GitHub integration. - [Measure delivery reliability and stability](/guides/all/measure-reliability-and-stability.md): Enable agents and developers to measure reliability and stability of your delivery pipeline by tracking workflow failure rates and PRs blocked by failing CI/CD using Port's GitHub integration. #### measure-sprint-health-with-jira Enable agents and developers to evaluate sprint health using Jira metrics, Port dashboards, and scorecards. - [Measure sprint health with Jira](/guides/all/measure-sprint-health-with-jira.md): Enable agents and developers to evaluate sprint health using Jira metrics, Port dashboards, and scorecards. #### measure-standards Enable agents and developers to measure standards compliance across your software delivery, tracking quality, security, and organizational alignment using Port's GitHub integration and scorecards. - [Track standards adherence](/guides/all/measure-standards.md): Enable agents and developers to measure standards compliance across your software delivery, tracking quality, security, and organizational alignment using Port's GitHub integration and scorecards. #### measure-the-roi-of-skills-in-your-org-registry Measure whether skills in your registry are worth their cost by connecting real Claude skill-usage and spend data to a Skill ROI scorecard and dashboard. - [Measure the ROI of skills in your org registry](/guides/all/measure-the-roi-of-skills-in-your-org-registry.md): Measure whether skills in your registry are worth their cost by connecting real Claude skill-usage and spend data to a Skill ROI scorecard and dashboard. #### migrate-project-from-github-to-gitlab Enable agents and developers to migrate repositories from GitHub to GitLab using a Port workflow and GitHub Actions. - [Migrate repositories from GitHub to GitLab using Port workflows](/guides/all/migrate-project-from-github-to-gitlab.md): Enable agents and developers to migrate repositories from GitHub to GitLab using a Port workflow and GitHub Actions. #### monitor-integrations Enable agents and developers to monitor and manage your Port Ocean integrations for improved visibility and performance tracking. - [Monitor integrations](/guides/all/monitor-integrations.md): Enable agents and developers to monitor and manage your Port Ocean integrations for improved visibility and performance tracking. #### move-port-configurations-between-environments-with-port-ai-and-github-mcp Enable agents and developers to back up Port blueprints and actions to GitHub from one environment and import them into another environment using Port AI and GitHub MCP. - [Sync Port configurations between environments using Port AI and MCP](/guides/all/move-port-configurations-between-environments-with-port-ai-and-github-mcp.md): Enable agents and developers to back up Port blueprints and actions to GitHub from one environment and import them into another environment using Port AI and GitHub MCP. #### notify-users-upon-approval-of-action Enable agents and developers to close the request feedback loop with automated Slack notifications for approved or declined Port actions. - [Notify users upon approval of self-service actions](/guides/all/notify-users-upon-approval-of-action.md): Enable agents and developers to close the request feedback loop with automated Slack notifications for approved or declined Port actions. #### nudge-pr-reviewers-with-port-workflows Enable agents and developers to keep pull request reviews moving with automated reminders from Port Workflows. - [Nudge Pull Request Reviewers (Port Workflows)](/guides/all/nudge-pr-reviewers-with-port-workflows.md): Enable agents and developers to keep pull request reviews moving with automated reminders from Port Workflows. #### onboard-developers-from-internal-docs-using-port-mcp Enable agents and developers to set up onboarding guidance in Port and Cursor using Port MCP, a connected Notion MCP connector, and a custom onboarding skill. - [Onboard developers using internal docs and Port MCP](/guides/all/onboard-developers-from-internal-docs-using-port-mcp.md): Enable agents and developers to set up onboarding guidance in Port and Cursor using Port MCP, a connected Notion MCP connector, and a custom onboarding skill. #### owasp-top-10-scorecard Enable agents and developers to build an OWASP Top 10 security scorecard in Port using vulnerability data from Snyk. - [Build an OWASP Top 10 Scorecard](/guides/all/owasp-top-10-scorecard.md): Enable agents and developers to build an OWASP Top 10 security scorecard in Port using vulnerability data from Snyk. #### prepare-work-items-with-ai Enable agents and developers to use Port AI to enrich work items with context lake data, prepare them for human review, and route approved tickets to coding agents. - [Prepare work items with AI](/guides/all/prepare-work-items-with-ai.md): Enable agents and developers to use Port AI to enrich work items with context lake data, prepare them for human review, and route approved tickets to coding agents. #### promote-to-production-workflow Enable agents and developers to build a "Promote to production" flow using Port Workflows and GitHub Actions, with safety checks, deployment tracking, automated PagerDuty incident creation, and team notifications. - [Promote to production workflow](/guides/all/promote-to-production-workflow.md): Enable agents and developers to build a "Promote to production" flow using Port Workflows and GitHub Actions, with safety checks, deployment tracking, automated PagerDuty incident creation, and team notifications. #### provision-azure-resources-with-ado-mcp Build a Port workflow that reads real Terraform module source from Azure DevOps via MCP, then triggers a Claude Code pipeline to open a pull request that follows your module conventions. - [Provision Azure resources from Terraform with Azure DevOps MCP](/guides/all/provision-azure-resources-with-ado-mcp.md): Build a Port workflow that reads real Terraform module source from Azure DevOps via MCP, then triggers a Claude Code pipeline to open a pull request that follows your module conventions. #### provision-cloud-resources-with-agentic-iac-workflow Build a self-service workflow that fetches service context, drafts an IaC implementation plan with AI, and opens a real pull request against your GitOps repository. - [Provision cloud resources with an agentic IaC workflow](/guides/all/provision-cloud-resources-with-agentic-iac-workflow.md): Build a self-service workflow that fetches service context, drafts an IaC implementation plan with AI, and opens a real pull request against your GitOps repository. #### query-dora-metrics-using-port-mcp Enable agents and developers to query DORA performance data from their IDE using Port MCP. - [Query DORA metrics using Port MCP](/guides/all/query-dora-metrics-using-port-mcp.md): Enable agents and developers to query DORA performance data from their IDE using Port MCP. #### remediate-ci-cd-failures-with-port-cursor-workflow Enable agents and developers to automatically remediate failed CI workflow runs by orchestrating Port, Cursor automations, and Slack notifications. - [Remediate CI/CD failures with Port AI & Cursor](/guides/all/remediate-ci-cd-failures-with-port-cursor-workflow.md): Enable agents and developers to automatically remediate failed CI workflow runs by orchestrating Port, Cursor automations, and Slack notifications. #### remediate-vulnerability-with-ai Use a Port workflow with AI and a coding agent (Claude Code, GitHub Copilot, or Gemini) to enrich critical security vulnerabilities and trigger automated fixes - [Remediate vulnerabilities with AI](/guides/all/remediate-vulnerability-with-ai.md): Use a Port workflow with AI and a coding agent (Claude Code, GitHub Copilot, or Gemini) to enrich critical security vulnerabilities and trigger automated fixes #### remediate-vulnerability-with-n8n-and-port Enable agents and developers to use Port as a context lake for n8n workflows to enrich vulnerability management with service metadata, ownership, and dependencies. - [Remediate security vulnerability with n8n and Port AI](/guides/all/remediate-vulnerability-with-n8n-and-port.md): Enable agents and developers to use Port as a context lake for n8n workflows to enrich vulnerability management with service metadata, ownership, and dependencies. #### report-a-bug Enable agents and developers to report bugs in Jira through a governed Port workflow. - [Report a bug](/guides/all/report-a-bug.md): Enable agents and developers to report bugs in Jira through a governed Port workflow. #### respond-to-new-relic-alerts Enable agents and developers to respond to New Relic alerts by creating Jira issues or PagerDuty incidents through Port. - [Respond to New Relic alerts](/guides/all/respond-to-new-relic-alerts.md): Enable agents and developers to respond to New Relic alerts by creating Jira issues or PagerDuty incidents through Port. #### rollback-argocd-deployment Enable agents and developers to deploy or roll back Argo CD workloads through a governed Port workflow backed by GitHub Actions. - [Roll back an ArgoCD deployment via GitHub Actions](/guides/all/rollback-argocd-deployment.md): Enable agents and developers to deploy or roll back Argo CD workloads through a governed Port workflow backed by GitHub Actions. #### route-scorecard-failures-to-jira-issues Turn failing scorecard rules into routed Jira issues with Port workflows and Port AI. - [Route scorecard failures to Jira issues with AI](/guides/all/route-scorecard-failures-to-jira-issues.md): Turn failing scorecard rules into routed Jira issues with Port workflows and Port AI. #### scaffold-a-new-service-with-port-workflows Enable agents and developers to scaffold new services through governed Port Workflows. - [Scaffold a new service](/guides/all/scaffold-a-new-service-with-port-workflows.md): Enable agents and developers to scaffold new services through governed Port Workflows. #### scaffold-a-new-service Enable agents and developers to scaffold production-ready services through governed Port self-service actions. - [Scaffold a new service with Actions](/guides/all/scaffold-a-new-service.md): Enable agents and developers to scaffold production-ready services through governed Port self-service actions. #### scaffold-repositories-using-cookiecutter Enable agents and developers to scaffold GitHub, Bitbucket, and Azure DevOps repositories using Cookiecutter templates via Port self-service actions. - [Scaffold repositories using Cookiecutter](/guides/all/scaffold-repositories-using-cookiecutter.md): Enable agents and developers to scaffold GitHub, Bitbucket, and Azure DevOps repositories using Cookiecutter templates via Port self-service actions. #### self-heal-scorecards-with-ai Enable agents and developers to use Port's AI capabilities to detect scorecard degradation and automatically use a coding agent like GitHub Copilot to fix it. - [Auto-fix services when scorecards degrade](/guides/all/self-heal-scorecards-with-ai.md): Enable agents and developers to use Port's AI capabilities to detect scorecard degradation and automatically use a coding agent like GitHub Copilot to fix it. #### send-slack-message-to-user Enable agents and developers to send Slack messages to users using Port's Slack app and Port Workflows. - [Send Slack messages to users using Port Workflows](/guides/all/send-slack-message-to-user.md): Enable agents and developers to send Slack messages to users using Port's Slack app and Port Workflows. #### setup-branch-protection-rules Enable agents and developers to enforce repository governance by evaluating GitHub branch protection rules with Port scorecards. - [Enforce branch protection rules with scorecards](/guides/all/setup-branch-protection-rules.md): Enable agents and developers to enforce repository governance by evaluating GitHub branch protection rules with Port scorecards. #### setup-delivery-performance-scorecard Enable agents and developers to set up Delivery Performance scorecards in Port to grade services, teams, groups, and organizations on PR/MR throughput, cycle time, and stale PR/MR management using Bronze, Silver, and Gold maturity levels. - [Set up delivery performance scorecards](/guides/all/setup-delivery-performance-scorecard.md): Enable agents and developers to set up Delivery Performance scorecards in Port to grade services, teams, groups, and organizations on PR/MR throughput, cycle time, and stale PR/MR management using Bronze, Silver, and Gold maturity levels. #### setup-dora-metrics-jira Enable agents and developers to configure DORA metrics using Jira issues as the deployment and incident source instead of GitHub and PagerDuty. - [Set up deployments and incidents using Jira issues](/guides/all/setup-dora-metrics-jira.md): Enable agents and developers to configure DORA metrics using Jira issues as the deployment and incident source instead of GitHub and PagerDuty. #### setup-dora-metrics Set up DORA metrics in Port: model deployments and incidents, then build a dashboard tracking all four delivery metrics. - [Track DORA metrics](/guides/all/setup-dora-metrics.md): Set up DORA metrics in Port: model deployments and incidents, then build a dashboard tracking all four delivery metrics. #### setup-engineering-intelligence-data-model Enable agents and developers to build Port's Engineering Intelligence data model across organizations, teams, services, and repositories. - [Create foundational Engineering Intelligence data model](/guides/all/setup-engineering-intelligence-data-model.md): Enable agents and developers to build Port's Engineering Intelligence data model across organizations, teams, services, and repositories. #### setup-incident-manager-ai-agent Enable agents and developers to set up an Incident Manager AI agent to help developers track and manage incidents efficiently. - [Set up the Incident Manager AI agent](/guides/all/setup-incident-manager-ai-agent.md): Enable agents and developers to set up an Incident Manager AI agent to help developers track and manage incidents efficiently. #### setup-pci-dss-code-compliance Enable agents and developers to measure PCI DSS code security compliance using Snyk data and Port scorecards. - [PCI DSS code security scorecard](/guides/all/setup-pci-dss-code-compliance.md): Enable agents and developers to measure PCI DSS code security compliance using Snyk data and Port scorecards. #### setup-pipeline-reliability-scorecard Enable agents and developers to set up Pipeline Reliability scorecards in Port to grade services, teams, groups, and organizations on CI/CD workflow failure rates and reliability trends using Bronze, Silver, and Gold maturity levels. - [Set up pipeline reliability scorecards](/guides/all/setup-pipeline-reliability-scorecard.md): Enable agents and developers to set up Pipeline Reliability scorecards in Port to grade services, teams, groups, and organizations on CI/CD workflow failure rates and reliability trends using Bronze, Silver, and Gold maturity levels. #### setup-platform-request-triage-ai-agent Enable agents and developers to set up a Platform Request Triage AI agent to help developers submit and track requests to the platform team. - [Set up the Platform Request Triage AI agent](/guides/all/setup-platform-request-triage-ai-agent.md): Enable agents and developers to set up a Platform Request Triage AI agent to help developers submit and track requests to the platform team. #### setup-pr-enricher-ai-agent Enable agents and developers to set up a Pull Request Enricher using Port Workflows to automatically comment on pull requests with additional context. - [Enrich pull requests using AI](/guides/all/setup-pr-enricher-ai-agent.md): Enable agents and developers to set up a Pull Request Enricher using Port Workflows to automatically comment on pull requests with additional context. #### setup-prd-agent-for-ticket-triage Enable agents and developers to turn thin tickets into clear specifications and route them to AI coding agents or people through governed Port workflows. - [Set up a PRD agent for ticket triage](/guides/all/setup-prd-agent-for-ticket-triage.md): Enable agents and developers to turn thin tickets into clear specifications and route them to AI coding agents or people through governed Port workflows. #### setup-production-readiness-scorecard Build Bronze to Gold production readiness checks on services so you can enforce ownership, documentation, and repository hygiene. - [Set up a service production readiness scorecard](/guides/all/setup-production-readiness-scorecard.md): Build Bronze to Gold production readiness checks on services so you can enforce ownership, documentation, and repository hygiene. #### setup-service-explorer-ai-agent Enable agents and developers to set up a Service Explorer AI agent with MCP to help developers discover services, understand ownership, and access documentation for quick onboarding. - [Set up the Service Explorer AI agent](/guides/all/setup-service-explorer-ai-agent.md): Enable agents and developers to set up a Service Explorer AI agent with MCP to help developers discover services, understand ownership, and access documentation for quick onboarding. #### setup-skills-registry Model every skill your org has written outside a developer's local machine as a Port entity, assign it to groups, and let people discover and install it through the UI, Port MCP, or the Port API. - [Set up a skills registry](/guides/all/setup-skills-registry.md): Model every skill your org has written outside a developer's local machine as a Port entity, assign it to groups, and let people discover and install it through the UI, Port MCP, or the Port API. #### setup-slack-reminders Enable agents and developers to deliver scheduled Slack reminders through Port automations. - [Slack reminders for scorecards](/guides/all/setup-slack-reminders.md): Enable agents and developers to deliver scheduled Slack reminders through Port automations. #### setup-task-manager-ai-agent Enable agents and developers to set up a Task Manager AI agent to help developers track and manage tasks efficiently. - [Set up the Task Manager AI agent](/guides/all/setup-task-manager-ai-agent.md): Enable agents and developers to set up a Task Manager AI agent to help developers track and manage tasks efficiently. #### ship-skills-through-one-golden-path Give every developer one standard way to propose a new skill: submit its SKILL.md, get a pull request opened automatically, and track the review from a single dashboard. - [Ship new skills through one golden path](/guides/all/ship-skills-through-one-golden-path.md): Give every developer one standard way to propose a new skill: submit its SKILL.md, get a pull request opened automatically, and track the review from a single dashboard. #### smart-pr-reviewer-assignment-using-port-ai Enable agents and developers to automatically assign qualified PR reviewers using Port AI, CODEOWNERS, ownership data, and capacity constraints. - [Smart PR reviewer assignment using AI](/guides/all/smart-pr-reviewer-assignment-using-port-ai.md): Enable agents and developers to automatically assign qualified PR reviewers using Port AI, CODEOWNERS, ownership data, and capacity constraints. #### standardize-ai-coding-rules Author AI coding rules in a source-of-truth git repo, provision them to subscribed repositories with a Port workflow, then ingest the files and grade compliance with a scorecard. - [Standardize AI coding rules across repositories](/guides/all/standardize-ai-coding-rules.md): Author AI coding rules in a source-of-truth git repo, provision them to subscribed repositories with a Port workflow, then ingest the files and grade compliance with a scorecard. #### streamline-iac-with-ai Enable agents and developers to use Port AI agents with AI coding assistants to streamline Infrastructure as Code (IaC) by automatically provisioning cloud resources like S3 buckets. - [Streamline IaC with AI](/guides/all/streamline-iac-with-ai.md): Enable agents and developers to use Port AI agents with AI coding assistants to streamline Infrastructure as Code (IaC) by automatically provisioning cloud resources like S3 buckets. #### surface-resource-actions-from-jira-with-ai Enable agents and developers to use Port's AI agent to analyze Jira issues and surface relevant self-service actions for cloud resources and permissions. - [Surface resource actions from Jira with AI](/guides/all/surface-resource-actions-from-jira-with-ai.md): Enable agents and developers to use Port's AI agent to analyze Jira issues and surface relevant self-service actions for cloud resources and permissions. #### sync-port-properties-to-github-external-custom-properties Sync Port property values to GitHub external custom properties, so you can search repositories and build GitHub rulesets on top of business context that lives in Port. - [Sync Port properties to GitHub external custom properties](/guides/all/sync-port-properties-to-github-external-custom-properties.md): Sync Port property values to GitHub external custom properties, so you can search repositories and build GitHub rulesets on top of business context that lives in Port. #### sync-service-entities-to-incident-io Enable agents and developers to use Port service context during incidents by synchronizing entities to incident.io. - [Sync Port services to incident.io](/guides/all/sync-service-entities-to-incident-io.md): Enable agents and developers to use Port service context during incidents by synchronizing entities to incident.io. #### terraform-plan-and-apply-aws-resource Enable agents and developers to plan and apply AWS infrastructure through governed Port self-service actions backed by Terraform. - [Provision Cloud Resource using Terraform Plan and Apply](/guides/all/terraform-plan-and-apply-aws-resource.md): Enable agents and developers to plan and apply AWS infrastructure through governed Port self-service actions backed by Terraform. #### track-ai-adoption-and-impact Enable agents and developers to track AI adoption across engineering teams and assess the impact of AI usage on delivery metrics like PR throughput and cycle time using Port's GitHub Ocean integration. - [Track AI adoption and impact](/guides/all/track-ai-adoption-and-impact.md): Enable agents and developers to track AI adoption across engineering teams and assess the impact of AI usage on delivery metrics like PR throughput and cycle time using Port's GitHub Ocean integration. #### track-ai-coding-assistants-costs Enable agents and developers to build a unified dashboard to analyze and compare AI coding tool costs across Cursor, Claude, and GitHub Copilot using data already ingested into Port. - [Track AI coding assistants costs](/guides/all/track-ai-coding-assistants-costs.md): Enable agents and developers to build a unified dashboard to analyze and compare AI coding tool costs across Cursor, Claude, and GitHub Copilot using data already ingested into Port. #### track-ai-driven-pull-requests Enable agents and developers to track and monitor AI-driven pull requests in your development workflow using Port's context lake and automation capabilities. - [Track AI-driven pull requests](/guides/all/track-ai-driven-pull-requests.md): Enable agents and developers to track and monitor AI-driven pull requests in your development workflow using Port's context lake and automation capabilities. #### track-and-show-mtbf-for-services Enable agents and developers to track Mean Time Between Failures (MTBF) for your Pagerduty services to measure reliability and identify improvement opportunities. - [Track and show MTBF for services](/guides/all/track-and-show-mtbf-for-services.md): Enable agents and developers to track Mean Time Between Failures (MTBF) for your Pagerduty services to measure reliability and identify improvement opportunities. #### track-gitlab-project-maturity-with-scorecards Enable agents and developers to enforce GitLab repository maturity standards using file-search signals and Port scorecards. - [Enforce code maturity with GitLab file search](/guides/all/track-gitlab-project-maturity-with-scorecards.md): Enable agents and developers to enforce GitLab repository maturity standards using file-search signals and Port scorecards. #### track-health-of-jira-projects Enable agents and developers to identify project risks by evaluating Jira issue hygiene and operational readiness with Port scorecards. - [Set up Jira project health scorecards](/guides/all/track-health-of-jira-projects.md): Enable agents and developers to identify project risks by evaluating Jira issue hygiene and operational readiness with Port scorecards. #### track-leaked-github-secrets-in-port Enable agents and developers to send leaked application secrets from GitHub to Port using Gitleaks. - [Track leaked GitHub secrets in Port](/guides/all/track-leaked-github-secrets-in-port.md): Enable agents and developers to send leaked application secrets from GitHub to Port using Gitleaks. #### track-slos-and-slis-for-services Enable agents and developers to track SLOs and SLIs for your services using Port. - [Track SLOs and SLIs for your services](/guides/all/track-slos-and-slis-for-services.md): Enable agents and developers to track SLOs and SLIs for your services using Port. #### track-team-work-allocation-with-jira Enable agents and developers to measure and visualize work allocation across Roadmap, Tech Debt, and Support categories at the team level using Jira labels and Port dashboards. - [Track team work allocation with Jira](/guides/all/track-team-work-allocation-with-jira.md): Enable agents and developers to measure and visualize work allocation across Roadmap, Tech Debt, and Support categories at the team level using Jira labels and Port dashboards. #### triage-incidents-with-dynatrace-mcp Triage incidents using AI, Dynatrace MCP, GitHub MCP, and Slack - fetch live telemetry with DQL, correlate code context, and post a structured root-cause summary from Port AI. - [Triage incidents with Dynatrace MCP](/guides/all/triage-incidents-with-dynatrace-mcp.md): Triage incidents using AI, Dynatrace MCP, GitHub MCP, and Slack - fetch live telemetry with DQL, correlate code context, and post a structured root-cause summary from Port AI. #### triage-incidents-with-mcp Triage incidents with live Sentry errors, GitHub code context, and Slack summaries, either on demand in Port AI or automatically when a new incident is created. - [Triage incidents using external MCP tools](/guides/all/triage-incidents-with-mcp.md): Triage incidents with live Sentry errors, GitHub code context, and Slack summaries, either on demand in Port AI or automatically when a new incident is created. #### trigger-ai-coding-assistants-from-port Learn how to trigger GitHub Copilot, Claude Code, and Google Gemini Assistant from Port using governed workflows. - [Trigger AI coding assistants from Port](/guides/all/trigger-ai-coding-assistants-from-port.md): Learn how to trigger GitHub Copilot, Claude Code, and Google Gemini Assistant from Port using governed workflows. #### trigger-datadog-incident-with-port-workflows Trigger a Datadog incident from a Port self-service workflow and sync it to your software catalog. - [Trigger Datadog incident (Workflows)](/guides/all/trigger-datadog-incident-with-port-workflows.md): Trigger a Datadog incident from a Port self-service workflow and sync it to your software catalog. #### trigger-datadog-incident Enable agents and developers to initiate Datadog incident response through a governed Port self-service action. - [Trigger Datadog Incident](/guides/all/trigger-datadog-incident.md): Enable agents and developers to initiate Datadog incident response through a governed Port self-service action. #### trigger-n8n-workflows-from-port Enable agents and developers to trigger existing n8n workflows from Port without changing n8n runtime or logic. - [Trigger n8n workflows from Port](/guides/all/trigger-n8n-workflows-from-port.md): Enable agents and developers to trigger existing n8n workflows from Port without changing n8n runtime or logic. #### trigger-spacelift-stack Enable agents and developers to trigger a Spacelift stack in Port to automate infrastructure workflows. - [Trigger a Spacelift stack](/guides/all/trigger-spacelift-stack.md): Enable agents and developers to trigger a Spacelift stack in Port to automate infrastructure workflows. #### visualize-and-manage-azure-devops-deployments Enable agents and developers to monitor and manage your Azure DevOps deployments using dashboards and self-service actions in Port. - [Visualize and manage your Azure DevOps deployments](/guides/all/visualize-and-manage-azure-devops-deployments.md): Enable agents and developers to monitor and manage your Azure DevOps deployments using dashboards and self-service actions in Port. #### visualize-and-manage-git-deployments Enable agents and developers to ingest, visualize, and manage GitHub and GitLab deployments with dashboards and self-service actions in Port. - [Visualize and manage Git deployments](/guides/all/visualize-and-manage-git-deployments.md): Enable agents and developers to ingest, visualize, and manage GitHub and GitLab deployments with dashboards and self-service actions in Port. #### visualize-and-manage-servicenow-incidents Enable agents and developers to monitor and manage your ServiceNow incidents using dashboards and Port workflows. - [Visualize and manage your ServiceNow incidents](/guides/all/visualize-and-manage-servicenow-incidents.md): Enable agents and developers to monitor and manage your ServiceNow incidents using dashboards and Port workflows. #### visualize-and-manage-statuspage-incidents Enable agents and developers to monitor and manage your Statuspage incidents and components using dashboards and self-service actions in Port. - [Visualize and manage your Statuspage incidents and components](/guides/all/visualize-and-manage-statuspage-incidents.md): Enable agents and developers to monitor and manage your Statuspage incidents and components using dashboards and self-service actions in Port. #### visualize-claude-metrics Enable agents and developers to create dashboards to monitor Claude API usage, track costs, analyze model performance, and measure workspace-level metrics. - [Visualize Claude metrics](/guides/all/visualize-claude-metrics.md): Enable agents and developers to create dashboards to monitor Claude API usage, track costs, analyze model performance, and measure workspace-level metrics. #### visualize-claude-skills-adoption Visualize how Claude Skills are adopted, shared and reused across your organization, which skills become part of regular workflows, and where skill-related spend is concentrated, using Port's Claude integration and dashboards. - [Visualize Claude Skills adoption and usage](/guides/all/visualize-claude-skills-adoption.md): Visualize how Claude Skills are adopted, shared and reused across your organization, which skills become part of regular workflows, and where skill-related spend is concentrated, using Port's Claude integration and dashboards. #### visualize-cursor-metrics Enable agents and developers to visualize Cursor adoption, suggestion acceptance, and engineering impact at the user, team, and organization level using Port's native Cursor integration and dashboards. - [Visualize Cursor metrics](/guides/all/visualize-cursor-metrics.md): Enable agents and developers to visualize Cursor adoption, suggestion acceptance, and engineering impact at the user, team, and organization level using Port's native Cursor integration and dashboards. #### visualize-github-copilot-metrics Enable agents and developers to visualize GitHub Copilot adoption, suggestion acceptance, and engineering impact at the user, team, and organization level using Port's GitHub Copilot integration and dashboards. - [Visualize GitHub Copilot metrics](/guides/all/visualize-github-copilot-metrics.md): Enable agents and developers to visualize GitHub Copilot adoption, suggestion acceptance, and engineering impact at the user, team, and organization level using Port's GitHub Copilot integration and dashboards. #### visualize-jira-lead-time Enable agents and developers to monitor Jira lead time at the project, service, and team levels using Port dashboards. - [Visualize lead time using Jira](/guides/all/visualize-jira-lead-time.md): Enable agents and developers to monitor Jira lead time at the project, service, and team levels using Port dashboards. #### visualize-okta-security-insights Enable agents and developers to build a security analytics dashboard for Okta identity and access management using Port's integration. - [Visualize Okta security insights](/guides/all/visualize-okta-security-insights.md): Enable agents and developers to build a security analytics dashboard for Okta identity and access management using Port's integration. #### visualize-service-argocd-runtime Enable agents and developers to visualize ArgoCD service runtime in Port, enhancing monitoring and management of your Kubernetes applications. - [Visualize your services' Kubernetes runtime using ArgoCD](/guides/all/visualize-service-argocd-runtime.md): Enable agents and developers to visualize ArgoCD service runtime in Port, enhancing monitoring and management of your Kubernetes applications. #### visualize-service-k8s-runtime Enable agents and developers to visualize Kubernetes service runtime in Port, improving monitoring and management of your cloud-native applications. - [Visualize your services' k8s runtime](/guides/all/visualize-service-k8s-runtime.md): Enable agents and developers to visualize Kubernetes service runtime in Port, improving monitoring and management of your cloud-native applications. #### visualize-your-wiz-vulnerabilities Enable agents and developers to monitor Wiz issues and gain security insights using dashboards. - [Visualize your Wiz security issues](/guides/all/visualize-your-wiz-vulnerabilities.md): Enable agents and developers to monitor Wiz issues and gain security insights using dashboards. #### work-item-blueprint Enable agents and developers to connect Jira issues with services, teams, pull requests, and deployments for autonomous ticket resolution. - [Implement the work item blueprint pattern](/guides/all/work-item-blueprint.md): Enable agents and developers to connect Jira issues with services, teams, pull requests, and deployments for autonomous ticket resolution. ## glossary Read precise definitions of Port terms such as blueprint, entity, and scorecard so you apply them correctly in setup and support. - [Glossary](/glossary.md): Read precise definitions of Port terms such as blueprint, entity, and scorecard so you apply them correctly in setup and support. ## solutions ### agentic-resource-management #### common-use-cases This is a catalog of common use cases we see deliver value across our customers. - [Common use cases](/solutions/agentic-resource-management/common-use-cases.md): This is a catalog of common use cases we see deliver value across our customers. #### getting-started This walkthrough shows how to build an agentic workflow that provisions a new cloud resource using Infrastructure as Code (IaC). A developer or agent expresses the intent to create a resource, and the workflow fetches context, generates an implementation plan, creates a resource request, and opens a pull request against your GitOps repository. - [Getting started](/solutions/agentic-resource-management/getting-started.md): This walkthrough shows how to build an agentic workflow that provisions a new cloud resource using Infrastructure as Code (IaC). A developer or agent expresses the intent to create a resource, and the workflow fetches context, generates an implementation plan, creates a resource request, and opens a pull request against your GitOps repository. #### golden-paths Golden paths are the foundation of resource management. They are how you deliver engineering velocity without giving up governance, for both developers and AI agents. - [Golden paths](/solutions/agentic-resource-management/golden-paths.md): Golden paths are the foundation of resource management. They are how you deliver engineering velocity without giving up governance, for both developers and AI agents. #### governance-at-scale As an organization scales, golden path workflows for day-0 and day-2 actions are no longer enough on their own. Thousands of resources across dozens of teams need oversight that doesn't depend on any one person watching every request. A few patterns help you keep control without becoming the bottleneck. - [Governance at scale](/solutions/agentic-resource-management/governance-at-scale.md): As an organization scales, golden path workflows for day-0 and day-2 actions are no longer enough on their own. Thousands of resources across dozens of teams need oversight that doesn't depend on any one person watching every request. A few patterns help you keep control without becoming the bottleneck. #### multi-cloud Golden paths and guardrails are only as good as their coverage. If they only reach one cloud, the gaps in every other environment keep the too-slow / too-loose tension alive. - [Multi-cloud single pane of glass](/solutions/agentic-resource-management/multi-cloud.md): Golden paths and guardrails are only as good as their coverage. If they only reach one cloud, the gaps in every other environment keep the too-slow / too-loose tension alive. #### overview Give developers and agents Port golden paths for infrastructure so you provision and decommission under platform team standards. - [Resource management](/solutions/agentic-resource-management/overview.md): Give developers and agents Port golden paths for infrastructure so you provision and decommission under platform team standards. ### autonomous-ticket-resolution #### agentic-initiatives Run one coding agent across every service that needs the same change, from a single governed initiative, tracked per service and as a portfolio. - [Agentic initiatives](/solutions/autonomous-ticket-resolution/agentic-initiatives.md): Run one coding agent across every service that needs the same change, from a single governed initiative, tracked per service and as a portfolio. ##### getting-started Build an agentic initiative that fans a coding agent out across every service failing a scorecard rule, with per-service tracking and portfolio-level progress. - [Getting started with agentic initiatives](/solutions/autonomous-ticket-resolution/agentic-initiatives/getting-started.md): Build an agentic initiative that fans a coding agent out across every service failing a scorecard rule, with per-service tracking and portfolio-level progress. #### code-review Enrich PRs with service context, assign smart reviewers, send review nudges, and track security review outputs in your catalog. - [Review the results](/solutions/autonomous-ticket-resolution/code-review.md): Enrich PRs with service context, assign smart reviewers, send review nudges, and track security review outputs in your catalog. #### getting-started-with-atr Build your first end-to-end Autonomous Ticket Resolution workflow. A Jira ticket arrives thin, gets enriched with catalog context, is scored for AI readiness, and is delegated to Cursor with one click. - [Getting started with your first Autonomous Ticket Resolution agentic workflow](/solutions/autonomous-ticket-resolution/getting-started-with-atr.md): Build your first end-to-end Autonomous Ticket Resolution workflow. A Jira ticket arrives thin, gets enriched with catalog context, is scored for AI readiness, and is delegated to Cursor with one click. #### overview Follow Port's autonomous ticket flow from triage to shipping, letting AI route enriched work while you retain key decision gates. - [Autonomous ticket resolution](/solutions/autonomous-ticket-resolution/overview.md): Follow Port's autonomous ticket flow from triage to shipping, letting AI route enriched work while you retain key decision gates. #### roi-dashboard Measure the impact of Autonomous Ticket Resolution (ATR) in Port with an ROI dashboard covering tickets resolved, time saved, and cost impact. - [Measure ATR's impact](/solutions/autonomous-ticket-resolution/roi-dashboard.md): Measure the impact of Autonomous Ticket Resolution (ATR) in Port with an ROI dashboard covering tickets resolved, time saved, and cost impact. #### safe-release Deploy code safely with PR enrichment, smart reviewer assignment, security review tracking, and automated review nudges. - [Release safely with control](/solutions/autonomous-ticket-resolution/safe-release.md): Deploy code safely with PR enrichment, smart reviewer assignment, security review tracking, and automated review nudges. #### turn-tickets-into-actionable-work Enrich Jira tickets with context, evaluate AI delegation eligibility, and produce routing decisions with supporting documentation. - [Turn tickets into actionable work](/solutions/autonomous-ticket-resolution/turn-tickets-into-actionable-work.md): Enrich Jira tickets with context, evaluate AI delegation eligibility, and produce routing decisions with supporting documentation. ##### overview Transform thin tickets into actionable work items with service context, blast radius, PRDs, and AI routing decisions. - [Ticket preparation](/solutions/autonomous-ticket-resolution/turn-tickets-into-actionable-work/overview.md): Transform thin tickets into actionable work items with service context, blast radius, PRDs, and AI routing decisions. ##### routing-decision Determine AI delegation eligibility with deterministic scorecards and LLM confidence scoring. Route work safely between agents and humans. - [Routing decision](/solutions/autonomous-ticket-resolution/turn-tickets-into-actionable-work/routing-decision.md): Determine AI delegation eligibility with deterministic scorecards and LLM confidence scoring. Route work safely between agents and humans. ##### work-item-blueprint-pattern Create a dedicated Port entity for AI-assisted development that tracks workflow state separately from Jira/Linear/ADO tickets. - [Work item blueprint pattern](/solutions/autonomous-ticket-resolution/turn-tickets-into-actionable-work/work-item-blueprint-pattern.md): Create a dedicated Port entity for AI-assisted development that tracks workflow state separately from Jira/Linear/ADO tickets. #### why-port Port provides catalog context, deterministic routing scorecards, entity persistence, and measurable outcomes for autonomous ticket resolution workflows. - [Why Port?](/solutions/autonomous-ticket-resolution/why-port.md): Port provides catalog context, deterministic routing scorecards, entity persistence, and measurable outcomes for autonomous ticket resolution workflows. ### engineering-intelligence #### ai-adoption Track AI tool adoption rates, measure ROI, and correlate usage with delivery performance improvements across teams. - [AI adoption and impact](/solutions/engineering-intelligence/ai-adoption.md): Track AI tool adoption rates, measure ROI, and correlate usage with delivery performance improvements across teams. ##### ai-adoption-survey Run a recurring AI Adoption & Impact pulse survey to complement usage telemetry with self-reported signal on velocity, time saved, quality, and focus. - [AI adoption survey](/solutions/engineering-intelligence/ai-adoption/ai-adoption-survey.md): Run a recurring AI Adoption & Impact pulse survey to complement usage telemetry with self-reported signal on velocity, time saved, quality, and focus. ##### ai-impact Correlate AI tool adoption with delivery performance, incident frequency, and code quality to measure real engineering outcomes. - [AI impact](/solutions/engineering-intelligence/ai-adoption/ai-impact.md): Correlate AI tool adoption with delivery performance, incident frequency, and code quality to measure real engineering outcomes. ##### ai-tools Monitor Copilot, Cursor, and AI tool usage rates, suggestion acceptance, and adoption distribution across teams for ROI analysis. - [Track AI adoption](/solutions/engineering-intelligence/ai-adoption/ai-tools.md): Monitor Copilot, Cursor, and AI tool usage rates, suggestion acceptance, and adoption distribution across teams for ROI analysis. ##### skills-usage Track Claude Skills adoption, depth of usage, and whether knowledge stays personal or becomes reusable across your organization. - [Skills usage](/solutions/engineering-intelligence/ai-adoption/skills-usage.md): Track Claude Skills adoption, depth of usage, and whether knowledge stays personal or becomes reusable across your organization. #### conversational-insights Query and analyze your context lake with AI — through Port agents, MCP-connected tools, or dashboards and reports. - [Conversational insights](/solutions/engineering-intelligence/conversational-insights.md): Query and analyze your context lake with AI — through Port agents, MCP-connected tools, or dashboards and reports. #### devex-surveys Run developer experience surveys, collect qualitative feedback, and turn developer sentiment into actionable improvement initiatives. - [DevEx surveys](/solutions/engineering-intelligence/devex-surveys.md): Run developer experience surveys, collect qualitative feedback, and turn developer sentiment into actionable improvement initiatives. #### getting-started Four-phase Engineering Intelligence journey from baseline metrics through continuous improvement with scorecards, AI agents, and automations. - [Getting started](/solutions/engineering-intelligence/getting-started.md): Four-phase Engineering Intelligence journey from baseline metrics through continuous improvement with scorecards, AI agents, and automations. #### measure-and-track-standards Measure DORA metrics, delivery performance, pipeline reliability, and production readiness - then enforce standards with scorecards. - [Measure and track standards](/solutions/engineering-intelligence/measure-and-track-standards.md): Measure DORA metrics, delivery performance, pipeline reliability, and production readiness - then enforce standards with scorecards. ##### delivery-performance Track PR throughput, cycle time, and stale PRs — then enforce delivery standards with scorecards graded by maturity tier. - [Delivery performance](/solutions/engineering-intelligence/measure-and-track-standards/delivery-performance.md): Track PR throughput, cycle time, and stale PRs — then enforce delivery standards with scorecards graded by maturity tier. ##### dora-metrics Measure the four DORA metrics per service and team, then enforce delivery maturity with DORA scorecards. - [DORA metrics](/solutions/engineering-intelligence/measure-and-track-standards/dora-metrics.md): Measure the four DORA metrics per service and team, then enforce delivery maturity with DORA scorecards. ##### pipeline-reliability Track CI/CD pipeline health and failure trends — then enforce reliability standards with scorecards and automated remediation. - [Reliability](/solutions/engineering-intelligence/measure-and-track-standards/pipeline-reliability.md): Track CI/CD pipeline health and failure trends — then enforce reliability standards with scorecards and automated remediation. ##### production-readiness Score services on ownership, documentation, monitoring, security compliance, and infrastructure hygiene with Bronze/Silver/Gold maturity levels. - [Production readiness](/solutions/engineering-intelligence/measure-and-track-standards/production-readiness.md): Score services on ownership, documentation, monitoring, security compliance, and infrastructure hygiene with Bronze/Silver/Gold maturity levels. #### overview Track DORA, delivery, reliability, and developer experience in Port, then find bottlenecks and improve from catalog-linked data. - [Engineering Intelligence](/solutions/engineering-intelligence/overview.md): Track DORA, delivery, reliability, and developer experience in Port, then find bottlenecks and improve from catalog-linked data. #### why-port Port combines engineering intelligence with orchestration on a unified catalog. Close the loop from insight to execution to measured impact. - [Why Port?](/solutions/engineering-intelligence/why-port.md): Port combines engineering intelligence with orchestration on a unified catalog. Close the loop from insight to execution to measured impact. ### overview Browse Port solutions for resource management, incidents, engineering intelligence, and ticket resolution with humans and agents. - [Solutions overview](/solutions/overview.md): Browse Port solutions for resource management, incidents, engineering intelligence, and ticket resolution with humans and agents. ### self-healing-incidents #### act-on-response Execute pre-authorized actions inside boundaries the system enforces structurally, so autonomy scales without breaking accountability. - [Act on response](/solutions/self-healing-incidents/act-on-response.md): Execute pre-authorized actions inside boundaries the system enforces structurally, so autonomy scales without breaking accountability. #### detect-incidents Score confidence from converging signals instead of a single threshold, and declare only once that confidence clears a threshold set in advance. - [Detect incidents](/solutions/self-healing-incidents/detect-incidents.md): Score confidence from converging signals instead of a single threshold, and declare only once that confidence clears a threshold set in advance. #### diagnose-incidents Reconstruct what changed and quantify impact on the incident record, so remediation targets the real cause instead of the first plausible one. - [Diagnose incidents](/solutions/self-healing-incidents/diagnose-incidents.md): Reconstruct what changed and quantify impact on the incident record, so remediation targets the real cause instead of the first plausible one. #### learn-from-incidents Turn divergence between expected and observed outcomes into a proposed change, reviewed by a human, before the next incident. - [Learn from incidents](/solutions/self-healing-incidents/learn-from-incidents.md): Turn divergence between expected and observed outcomes into a proposed change, reviewed by a human, before the next incident. #### overview Follow five incident stages in Port so agents detect, diagnose, recommend, act, and learn inside gates your team defines up front. - [Self-healing incidents](/solutions/self-healing-incidents/overview.md): Follow five incident stages in Port so agents detect, diagnose, recommend, act, and learn inside gates your team defines up front. #### recommend-response Score response options against explicit criteria and present a ranked, explainable set, instead of collapsing straight to a single answer. - [Recommend response](/solutions/self-healing-incidents/recommend-response.md): Score response options against explicit criteria and present a ranked, explainable set, instead of collapsing straight to a single answer. ## troubleshooting Resolve Port problems using guides for Ocean integrations, actions, the execution agent, and GitHub, GitLab, or Azure DevOps. - [Troubleshooting](/troubleshooting.md): Resolve Port problems using guides for Ocean integrations, actions, the execution agent, and GitHub, GitLab, or Azure DevOps. ### actions Diagnose self-service actions in Port that are stuck in progress, including action backend configuration and Port execution agent setup for Git providers. - [Actions troubleshooting](/troubleshooting/actions.md): Diagnose self-service actions in Port that are stuck in progress, including action backend configuration and Port execution agent setup for Git providers. ### azure-devops Diagnose Azure DevOps Ocean integration bugs in Port - deleted repositories not reflected, syncs that run with no result, builds not fully ingesting, and inconsistent file ingestion. - [Azure DevOps integration troubleshooting](/troubleshooting/azure-devops.md): Diagnose Azure DevOps Ocean integration bugs in Port - deleted repositories not reflected, syncs that run with no result, builds not fully ingesting, and inconsistent file ingestion. ### general Answers to common general questions about Port - account setup, organization management, exporting catalog data, embedding content, and security. - [General troubleshooting](/troubleshooting/general.md): Answers to common general questions about Port - account setup, organization management, exporting catalog data, embedding content, and security. ### github Diagnose GitHub Ocean integration bugs in Port - stale data with live events, entities not deleted, partial PR ingestion, workflow run deletion, missing team relations, and migration from the legacy integration. - [GitHub integration troubleshooting](/troubleshooting/github.md): Diagnose GitHub Ocean integration bugs in Port - stale data with live events, entities not deleted, partial PR ingestion, workflow run deletion, missing team relations, and migration from the legacy integration. ### gitlab Diagnose GitLab Ocean integration bugs in Port, including projects not visible after setting up a group access token and version compatibility issues. - [GitLab integration troubleshooting](/troubleshooting/gitlab.md): Diagnose GitLab Ocean integration bugs in Port, including projects not visible after setting up a group access token and version compatibility issues. ### ocean-integrations Diagnose common Ocean integration issues in Port - installation failures, validation errors, mapping problems, and connectivity between the integration and Port's API. - [Ocean integrations troubleshooting](/troubleshooting/ocean-integrations.md): Diagnose common Ocean integration issues in Port - installation failures, validation errors, mapping problems, and connectivity between the integration and Port's API. ### port-agent Diagnose Port execution agent failures - 401 authentication errors, Kafka consumer group authorization, action execution timeouts, and TLS certificate issues on startup. - [Port execution agent troubleshooting](/troubleshooting/port-agent.md): Diagnose Port execution agent failures - 401 authentication errors, Kafka consumer group authorization, action execution timeouts, and TLS certificate issues on startup. ## Optional - [Port agent guide: API authentication, endpoints, and terminology for building with Port](https://docs.port.io/AGENTS.md): Quick reference for agents: authentication, regional base URLs, common API request patterns, and correct Port terminology. - [Port implementation guides by platform pillar](https://docs.port.io/llms/guides.txt): Browse every surviving Port guide grouped under Context lake, Workflows, Port AI, Agent management, Interface builder, Governance, or Platform administration. - [Port MCP server](https://docs.port.io/ai-interfaces/port-mcp-server/overview): Connect AI assistants to Port context, actions, search, and governed MCP connectors. - [Port MCP server installation](https://docs.port.io/ai-interfaces/port-mcp-server/installation): Configure Claude, Cursor, VS Code, Codex, and other MCP clients with Port. - [Port REST API reference (all endpoints)](https://docs.port.io/api-reference/port-api): Full Port REST API: authentication, base URLs, and every endpoint (entities, blueprints, actions, scorecards, workflows). Individual endpoints are omitted from this index to keep it concise; browse them at this page, or use the machine-readable OpenAPI spec at https://docs.port.io/apispec-port-api.yaml. For a single combined OpenAPI spec across all services (Port API, AI, workflow, asset, integration), use https://docs.port.io/unified-services.yaml. --- # Full Documentation Content # AGENTS.md - AI Documentation Maintenance Guide This is a hidden guide for AI agents to understand how to maintain and update the AI interfaces documentation in Port Docs. ## File Structure[​](#file-structure "Direct link to File Structure") The AI interfaces documentation files are located in: * `/docs/agent-management/` - Main documentation files * `/src/data/mcpTools.js` - MCP tool definitions (JSON file) * `/src/components/ToolsList/` - React component that displays tools from `mcpTools.js` ## Common Tasks[​](#common-tasks "Direct link to Common Tasks") ### Update MCP Tools[​](#update-mcp-tools "Direct link to Update MCP Tools") To add, update, or remove MCP tools: 1. **Update the file**: `src/data/mcpTools.js` 2. **Ask for clarification**: Determine if the tool is for `builder` only or for both `developer` and `builder` roles 3. The `ToolsList` component automatically reads from this file and displays tools in the documentation **Tool naming conventions:** * List tools (`list_blueprints`, `list_entities`, etc.): Support optional `identifiers` parameter to get detailed info for specific items * Upsert tools (`upsert_blueprint`, `upsert_entity`, etc.): Create or update resources (creates if not exists, updates if exists) * Delete tools: Remove resources ### Set Feature Beta Status[​](#set-feature-beta-status "Direct link to Set Feature Beta Status") When asked to mark a feature as **open beta** or **closed beta**, use the appropriate component: #### For Open Beta Features[​](#for-open-beta-features "Direct link to For Open Beta Features") Use the `BetaFeatureNotice` component: ``` import BetaFeatureNotice from '/docs/generalTemplates/_beta_feature_notice.md' ``` **Location**: Place this after the page title (H1) and before the main content. #### For Closed Beta Features[​](#for-closed-beta-features "Direct link to For Closed Beta Features") Use the `ClosedBetaFeatureNotice` component: ``` import ClosedBetaFeatureNotice from '/docs/generalTemplates/_closed_beta_feature_notice.md' ``` **Location**: Place this after the page title (H1) and before the main content. **ID**: Use a specific feature identifier (e.g., `"slack-app"`). Check `/docs/generalTemplates/_closed_beta_feature_notice_defs.js` for available IDs and their custom messages. You might need to create a new definition, ask the user for this. ### Add New LLM Model[​](#add-new-llm-model "Direct link to Add New LLM Model") To add a new LLM model to the documentation: 1. **Identify the model name and providers**: Determine the exact model identifier (e.g., `gpt-5`) and which providers support it (e.g., Anthropic, AWS Bedrock). 2. **Update the main documentation file**: Edit `docs/port-ai/llm-providers-management/overview.md`: 3. **Note about API reference files**: The API reference files in `docs/api-reference/` (like `general-purpose-ai-interactions.api.mdx`, `invoke-a-specific-agent.api.mdx`, etc.) are auto-generated from OpenAPI specifications. These will be updated automatically when the backend API is updated. You don't need to manually edit these files. ### Update MCP Installation Instructions[​](#update-mcp-installation-instructions "Direct link to Update MCP Installation Instructions") The MCP installation instructions are maintained in a reusable component that is imported into the installation page. **Location**: `/docs/generalTemplates/_mcp-installation.md` **Usage**: This component is imported in `docs/agent-management/port-mcp-server/installation.md`. **What you could update**: * Add new client support * Update client instructions * Update disclaimers/warnings/admonitions ### Update Monthly Limits[​](#update-monthly-limits "Direct link to Update Monthly Limits") When asked to update the monthly quota/limit for AI invocations: 1. **Identify all locations**: Search for mentions of monthly limits/quota in the AI interfaces documentation: * `docs/port-ai/limits-and-quotas.md` - Contains rate limits section and example JSON responses 2. **Update text descriptions**: Replace the quota number in all text descriptions (e.g., "20 AI invocations per month" → "50 AI invocations per month") 3. **Update example JSON**: Update the `monthlyLimit` value in example JSON responses to match the new limit. Also update `remainingQuota` to be one less than the limit (e.g., if limit is 50, remainingQuota should be 49) 4. **Files to check**: * `docs/port-ai/limits-and-quotas.md` - Lines with "Monthly quota" section and JSON examples 5. **Search pattern**: Look for patterns like: * "20 AI invocations per month" * `"monthlyLimit": 20` * "Default quota: 20" **Note**: The API reference files in `docs/api-reference/` are auto-generated from OpenAPI specifications and will be updated automatically when the backend API is updated. You don't need to manually edit these files. ### Update cross-page links[​](#update-cross-page-links "Direct link to Update cross-page links") When documentation pages move, update links to their new public URLs while preserving any heading anchors. Keep the surrounding content unchanged unless the move requires a content update. ## Other Tasks[​](#other-tasks "Direct link to Other Tasks") For other tasks not listed above, ask for clarification from the user. Once understood, add a new section to the "Common Tasks" section of this file. --- # AI Gateway Every agent your organization runs eventually calls a model. Multiply that across the agents you ship, and the traffic behind those calls becomes infrastructure in its own right. That infrastructure needs the same routing, cost control, and governance you expect from anything else in your stack. This page covers what an AI gateway is, why the agentic SDLC makes it urgent, where the gateway layer runs out of road, and how Port picks up from there. ## What is an AI gateway?[​](#what-is-an-ai-gateway "Direct link to What is an AI gateway?") An AI gateway is a single layer that every model call passes through before it reaches a provider such as OpenAI, Anthropic, AWS Bedrock, Azure, or Databricks. It exists so routing, cost control, and safety logic live in one place, instead of being copy-pasted into every app and agent that calls an LLM. LiteLLM, Vercel AI Gateway, and Bifrost are three established options doing this today. Different implementations, same core job: one control point standing in front of many providers. A gateway typically gives you: * **A unified API.** One interface across every provider, instead of a different integration per model. * **Routing and failover.** Traffic keeps flowing when a model or provider degrades. * **Budgets and rate limits.** Spend gets capped before it becomes a surprise. * **Guardrails.** Safety and compliance checks enforced in-line, on every call. * **Observability.** Logs, traces, and cost data per request. * **An MCP / tool gateway.** One front door for the external tools agents reach for. ## Why the agentic SDLC makes this urgent[​](#why-the-agentic-sdlc-makes-this-urgent "Direct link to Why the agentic SDLC makes this urgent") API gateways have existed for years. What has changed is who is making the calls. **Agents multiply call volume.** A single task can fan out into dozens of model calls chained across tools, where a human doing the same task might have made one API call, if any. Traffic that used to scale with headcount now scales with how many agents you have turned loose. **Tool and MCP sprawl compounds it.** Agents do not just call models. They reach for external tools and data sources constantly, and each one is a new integration point with its own access surface. **Cost stops being predictable.** Usage now scales with agent autonomy, not release cadence. A single agent given more latitude can generate a month's typical spend in a day, with no code deploy to flag it. **Mistakes propagate faster.** An ungoverned agent can act on a bad model response, call the wrong tool, or write to the wrong system before a human ever sees it. There is no code review step in the loop the way there is for a human-written pull request. An AI gateway is the layer that makes this survivable: one place to route, cap, and guard traffic instead of trusting every agent and app to do it correctly on its own. ## What gateways still miss[​](#what-gateways-still-miss "Direct link to What gateways still miss") A gateway solves the traffic problem. It does not solve the organizational one. Four gaps show up consistently once agents are running through it at scale. ### Traffic, not organization[​](#traffic-not-organization "Direct link to Traffic, not organization") A gateway sees the key that called the model. It does not see the team that owns that key, or the cost behind it. That gap matters twice over. Without ownership, an orphaned key (left behind after the agent it belonged to was retired) keeps spending indefinitely, because nothing flags that it should have been revoked. Without team attribution, "which team is driving this month's spend" turns into a manual reconciliation instead of a query anyone can run themselves. ### Config-driven, not workflow-driven[​](#config-driven-not-workflow-driven "Direct link to Config-driven, not workflow-driven") Budgets, guardrails, and access are things you set in a config file or through an API. There is no built-in path for a developer to request a change, or for security to approve one. Agentic development moves at the pace of iteration, not release cycles. A developer testing a new agent needs a budget increase today, not after a config PR gets reviewed, so without a request path, they either wait or find a way around the guardrail entirely. ### A runtime view, cut off from everything else[​](#a-runtime-view-cut-off-from-everything-else "Direct link to A runtime view, cut off from everything else") A gateway can tell you which agents and MCP servers are live right now. It cannot tell you whose repo any of them came from. An agent's behavior is inseparable from the code and tools it is built from, so when that runtime view is not connected to the service or repo that owns it, an incident means cross-referencing two separate systems by hand, under time pressure. ### Dashboards without enforcement[​](#dashboards-without-enforcement "Direct link to Dashboards without enforcement") A gateway's built-in UI shows spend and usage, but it stops at visibility. There is no mechanism to turn "this agent has no guardrail" into a standard the org actually enforces. As the number of agents, tools, and keys needing governance grows faster than any team can review by hand, a dashboard that only shows the problem becomes one more thing nobody has time to act on. ## How Port closes the gap[​](#how-port-closes-the-gap "Direct link to How Port closes the gap") Port does not replace the gateway. It sits on top of it, turning gateway data into something the rest of the org can see, act on, and enforce. ### One catalog, fully connected[​](#one-catalog-fully-connected "Direct link to One catalog, fully connected") Port pulls every key, budget, guardrail, agent, and MCP server the gateway manages into the same [software catalog](/context-lake/overview/.md) as the rest of your engineering org, related to the team and service that actually owns each one. An agent is just another artifact your org ships. It belongs next to the service and repo it comes from, not off in a system only one team ever opens. This is also what turns cost attribution from a manual exercise into a standing report: because every key is tied to a team, "who's driving spend" is a query, not a reconciliation project. See [External agents](/agent-management/external-agents/overview/.md) and the [agent registry](/agent-management/ai-registry/agent-registry/.md) for how Port inventories agents alongside the rest of your catalog. ### Internal tools carry the same risk[​](#internal-tools-carry-the-same-risk "Direct link to Internal tools carry the same risk") A gateway governs which MCP servers an agent can reach, but MCPs are not the only tools an agent calls. Port exposes your organization's own [self-service actions](/workflows/actions-and-automations/overview/.md) and golden paths as tools too, and those need the same governance question an MCP does. They are already org-approved business logic, but approved to exist is not the same as safe for any agent to invoke unsupervised. As agents take on more of the SDLC, the tools they reach for are increasingly internal, not external APIs, so governance that stops at MCPs misses the fastest-growing category of what agents can actually do. Port's governed [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md) and [Port MCP server](/agent-management/port-mcp-server/overview/.md) apply the same access-scoping logic here, regardless of whether the tool is an MCP server or an internal workflow. ### Gateway operations become self-service[​](#gateway-operations-become-self-service "Direct link to Gateway operations become self-service") Issuing a key, requesting a budget increase, testing a guardrail, or invoking a registered agent can each become a governed Port action, complete with approvals and an audit trail, instead of a direct call to the proxy. Agent development iterates fast enough that any manual step (filing a ticket for a key, pinging platform on Slack) becomes the actual bottleneck in the loop, not the model call itself. ### Complete governance across the whole AI stack[​](#complete-governance-across-the-whole-ai-stack "Direct link to Complete governance across the whole AI stack") Port [scorecards](/governance/standards-and-compliance/overview/.md) extend the same standards you already apply to services and pipelines to the gateway's agents, guardrails, and keys, so an ungoverned agent gets caught by the same mechanism as an undocumented service. Two pieces make this hold up as agents scale: * **Unified agent creation.** Every agent gets scaffolded through Port, so the right traffic rules apply from the moment it exists, not bolted on afterward. See [Build an AI agent](/agent-management/custom-agents/build-an-ai-agent/.md) and [External agents](/agent-management/external-agents/overview/.md). * **Dynamic policies.** Budget caps, tool access, and creation permissions can flex based on context (who is asking, what they are doing) instead of a static rule that either blocks legitimate work or lets risky work through. See [dynamic permissions](/workflows/actions-and-automations/create-self-service-experiences/set-self-service-actions-rbac/dynamic-permissions/examples/.md) for the same pattern on self-service actions. Once agents outnumber the services they support, governance has to be built into how they are created, not a step someone remembers to run. ## Related capabilities[​](#related-capabilities "Direct link to Related capabilities") | You need to… | Port capability | | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | Inventory agents alongside services and teams | [External agents](/agent-management/external-agents/overview/.md) and [agent registry](/agent-management/ai-registry/agent-registry/.md) | | Govern which MCP servers and skills agents may use | [AI registry](/agent-management/ai-registry/skills/overview/.md) | | Connect IDEs and agents to your catalog | [Port MCP server](/agent-management/port-mcp-server/overview/.md) | | Enforce standards on agents, keys, and guardrails | [Scorecards](/governance/standards-and-compliance/overview/.md) | The gateway routes the traffic. Port governs what runs through it. --- # Agent registry An agent registry is a governed inventory of external AI agents — both **code-first** agents built with SDKs and **cloud-managed** agents on platforms without a native Port connector — stored as entities in your Context Lake. Each registry entity tracks platform, status, model, region, and ownership so your organization can discover what exists instead of rebuilding agents in every squad. ![AI Agent Registry catalog with metadata columns](/img/guides/aiAgentRegistryCatalog.png)

Port's built-in [`_ai_agent`](/context-lake/data-model/setup-blueprint/default-blueprints/.md) blueprint can represent agents in your Context Lake. A dedicated registry blueprint (for example `aiAgent`) adds the metadata platform teams need for governance: approval status, owning team, deployment region, and which cloud platform hosts the agent. ## What it solves[​](#what-it-solves "Direct link to What it solves") | Challenge | How a registry helps | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | **No cross-platform visibility** | View Bedrock, Azure AI Foundry, and other cloud agents in one place in your Context Lake instead of checking each vendor console. | | **Duplicate agents** | Developers can search the registry before building a new agent and reuse what already exists. | | **Governance and compliance** | Track which models, regions, and statuses each agent uses for audit and policy reviews. | ## When to use a registry vs a native connector[​](#when-to-use-a-registry-vs-a-native-connector "Direct link to When to use a registry vs a native connector") | Approach | Best for | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **Native connector** | Platforms with a dedicated Port integration that syncs agents, sessions, and related resources automatically. | | **Custom agent registry** | Multi-cloud visibility when you ingest agents from Bedrock, Azure AI Foundry, or other APIs through a custom Ocean integration. | ## Set up your registry[​](#set-up-your-registry "Direct link to Set up your registry") For blueprint JSON, integration mapping, and step-by-step setup for AWS Bedrock and Azure AI Foundry, see the guide [Build an AI Agent Registry](/guides/all/build-ai-agent-registry/.md). ## Where to go next[​](#where-to-go-next "Direct link to Where to go next") * [Managing AI primitives](/agent-management/ai-registry/managing-ai-primitives/.md) — skills, MCP, managed agents, context lake, and AI data controls in the AI registry. * [External agents overview](/agent-management/external-agents/overview/.md) — how Port connects to agents hosted outside Port. * [Skills registry](/agent-management/ai-registry/skills/skills-registry/.md) — govern reusable skills alongside your agent inventory. * [MCP registry](/agent-management/ai-registry/mcp-registry/.md) — govern which MCP servers teams may use. --- # Managing AI primitives Port does not replace your coding agents. It gives them the same catalog, governance, and tooling context your teams already use so work can move from ticket to production safely. Use these docs when you need skills, MCP access, managed agents, or guardrails: * [Skills](/agent-management/ai-registry/skills/overview/.md) - reusable prompts and tool bundles your platform team can publish. * [Skills usage analytics](/agent-management/ai-registry/skills/skills-usage-analytics/.md) - measure which skills teams adopt in Cursor and Claude. * [Build an AI agent](/agent-management/custom-agents/build-an-ai-agent/.md) - configure a Port-managed agent against your catalog. * [Interact with AI agents](/agent-management/custom-agents/interact-with-ai-agents/.md) - trigger and track runs from Slack, the UI, or workflows. * [Port MCP server](/agent-management/port-mcp-server/overview/.md) - connect MCP clients to Port actions, entities, and scorecards. * [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md) - managed connectors for external MCP servers. * [Context Lake](/context-lake/overview/.md) - unified engineering context for accurate answers and actions. * [Security and data controls for Port AI](/port-ai/security-and-data-controls/.md) - how Port handles AI-related data and access. For the full ATR story, start with [Autonomous ticket resolution overview](/solutions/autonomous-ticket-resolution/overview/.md). --- # MCP connectors MCP Connectors turn Port into a unified MCP gateway, routing requests from developers and AI agents to external MCP servers while applying Port's governance, RBAC, and audit controls. Platform engineers configure which MCP servers are available and which tools are exposed. Developers and AI agents access everything through a single interface. [YouTube video player](https://www.youtube.com/embed/6VBhFq5SJ0s) ## Why MCP connectors?[​](#why-mcp-connectors "Direct link to Why MCP connectors?") Organizations building AI agents need access to tools beyond Port's native integrations. For example, internal documentation, custom systems, third-party platforms. Without MCP Connectors, teams either run parallel MCP systems (creating confusion) or build their own gateway infrastructure (recreating governance from scratch). MCP Connectors solve three critical challenges: * **Unified developer experience**: Connect once to Port's MCP gateway instead of managing multiple separate integrations. Developers access Port's native tools and all approved external MCPs through one interface. * **Governance and control**: Platform engineers choose which MCPs are approved, which tools are exposed, and who can access them. Complete audit trails and RBAC enforcement apply to all tool invocations. * **Holistic data access**: Port AI uses the [Context Lake](/context-lake/overview/.md) for structured organizational data and MCP Connectors for ephemeral or time-series data that doesn't belong in your data model. Refer to the following [article](https://www.port.io/blog/why-ai-agents-need-a-context-lake) to learn more about why agents need both types of data. ## Integrations vs. MCP connectors[​](#integrations-vs-mcp-connectors "Direct link to Integrations vs. MCP connectors") MCP connectors complement Port integrations rather than replace them — they serve different purposes and work best together. See [Integrations vs. MCP connectors](/context-lake/ingestion/integrations-vs-mcp/.md) for guidance on when to use each and when to use both. ## Setting up MCP connectors[​](#setting-up-mcp-connectors "Direct link to Setting up MCP connectors") Admins configure which MCP connectors are available organization-wide and control which tools each connector exposes. ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Admin role in Port. * Account credentials for the external tool you want to connect. ### Add a connector[​](#add-a-connector "Direct link to Add a connector") 1. Go to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Click on the `+ Connector` in the top-right corner. 3. Select the **MCP Servers** tab. 4. Choose from the available MCP servers (like Notion, Linear, Slack, GitLab, etc) or select **Custom Server** to add your own. 5. Fill in the connector details: * **Name** (required): A recognizable name for developers and AI agents. * **Description**: When this MCP should be used and for which use cases. This helps AI agents determine when to use this connector. * **URL** (required): The remote MCP server endpoint. * **Icon**: Select an icon to visually identify the connector. * **Team**: Assign ownership to a specific team. * **Headers**: Custom headers to include with requests (e.g., for API key authentication). 6. Click **Connect** and complete the authentication flow if required. 7. Under **Allowed Tools**, select which tools to expose to your organization using `+ Add Tool`. Only the tools you add will be visible to users. **Consider carefully before enabling write or delete capabilities** - users and AI agents will be able to perform these actions based on their permissions in the external tool. 8. Click `Publish` to make the connector available to users. Any remote MCP server You can connect any remote MCP server and not just the ones listed in the UI. Select **Custom Server** to add tools like Confluence, Kiro, or any other MCP-compatible service that provides a remote endpoint. **Advanced OAuth settings** For MCP servers that require manual OAuth configuration, you can expand the **Advanced OAuth Settings** section and provide: * **OAuth Client ID** and **OAuth Client Secret**: From the OAuth application you created in the external service. * **OAuth Scope**: The permissions to request during authentication. * **OAuth grant type**: Choose **Authorization code** for per-user or one-time organization consent, or **Client credentials** when the upstream server supports machine-to-machine OAuth for automated workflows. MCP connectors can also be added from the MCP connector catalog page. When adding from the catalog, an additional property is available: **Expose Port Connector**: Determines whether this entity is used as an active MCP connector. When enabled, the connector's tools are available to users through the AI assistant and Port MCP server. When disabled, the entity exists in the catalog but is not used as an MCP connector. In-chat configuration When a user asks for a tool that maps to an MCP server not yet configured for the organization, the Port AI assistant can offer to configure it inline by opening the same setup wizard. Users with permission to add MCP connectors see a configure option in chat. Users without that permission are told to ask an admin. ### Supported server types[​](#supported-server-types "Direct link to Supported server types") Port supports several types of remote MCP servers. All require **Name** and **URL**. Additional configuration depends on the authentication method: * **Dynamic client registration** (e.g., Notion, Slack): Servers that support [OAuth 2.0 Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591). Port automatically registers as an OAuth client - no additional configuration required. * **Manual client registration** (e.g., GitHub, GitLab): Servers that require you to create an OAuth application first. Configure **OAuth Client ID**, **OAuth Client Secret**, and optionally **OAuth Scope** under Advanced OAuth Settings. Choose **Authorization code** for per-user or one-time organization consent, or **Client credentials** when the upstream server supports machine-to-machine OAuth and you need a fully headless connection for automated workflows. * **API-based authentication**: Servers that use API keys or tokens. Add authentication credentials via **Headers** (e.g., `Authorization: Bearer `). * **No authentication** (e.g., GitMCP): Publicly accessible servers - no additional configuration required. ### Advanced MCP server configurations[​](#advanced-mcp-server-configurations "Direct link to Advanced MCP server configurations") Some MCP servers require more detailed configuration than just a URL and authentication headers. For example, servers that use manual OAuth client registration require you to first create an OAuth application in the external service and then paste the credentials into Port. See [GitHub MCP connector](/agent-management/ai-registry/mcp-connectors/github-mcp-connector/.md) for a step-by-step example of this setup. ### Reference organization secrets[​](#reference-organization-secrets "Direct link to Reference organization secrets") When configuring authentication (via headers or Advanced OAuth Settings), you can reference your organization's secrets using the following syntax: ``` {{ .secrets["YOUR_SECRET_KEY"] }} ``` **In headers:** ``` { "Authorization": "{{ .secrets[\"YOUR_SECRET_KEY\"] }}" } ``` **In Advanced OAuth Settings inputs:** Use the same syntax in the **OAuth Client ID** and **OAuth Client Secret** fields to reference stored secrets: ``` {{ .secrets["YOUR_SECRET_KEY"] }} ``` ## Authenticate to connectors[​](#authenticate-to-connectors "Direct link to Authenticate to connectors") Once your admin has configured MCP connectors, you need to authenticate your personal account to use them. Connectors configured with the **client credentials** grant type are the exception: clicking **Connect** authenticates them without a browser consent screen, and the resulting tokens are stored for the whole organization. ### From the MCP Servers menu[​](#from-the-mcp-servers-menu "Direct link to From the MCP Servers menu") 1. Open the [MCP Servers](https://app.port.io/organization/home?openModal=mcpServers) modal and select the **External MCP** tab. 2. You will see the MCP servers your admin has made available. 3. Click **Connect** on one and complete the OAuth authentication flow. 4. The MCP server is now available in all Port AI interfaces. ### From Port AI assistant[​](#from-port-ai-assistant "Direct link to From Port AI assistant") 1. Open the Port AI chat interface. 2. Click the **+** button to see available MCP servers. 3. Under **Need to Connect**, you will see servers that require authentication. 4. Click on one to initiate authentication. 5. Complete the OAuth flow. 6. After authenticating, the server appears under **Connectors** with the number of enabled tools (e.g., "9 of 10"). The assistant can prompt you When a task needs an MCP server that is configured for your organization but that you have not connected yet, the Port AI assistant surfaces a **Connect** card directly in the chat. Click **Connect** on the relevant server to complete the OAuth flow without leaving the conversation. ### Manage your tools[​](#manage-your-tools "Direct link to Manage your tools") After authenticating, you can toggle which tools are active for your account: 1. In the Port AI chat, click the **+** button. 2. Under **Connectors**, click the arrow next to an authenticated server. 3. Toggle individual tools on or off based on your needs. You can only toggle tools that your admin has enabled. Tools not added by the admin will not appear. ## Using MCP connectors[​](#using-mcp-connectors "Direct link to Using MCP connectors") After authenticating, you can use natural language to interact with external tools through any Port AI interface. **Example queries:** * "Search Notion for our API authentication documentation" * "Find the deployment runbook in Confluence" * "Show me open Jira tickets for the payments service" * "What alerts fired in the last hour?" ### IDE access[​](#ide-access "Direct link to IDE access") If you've connected your IDE to [Port's MCP server](/agent-management/port-mcp-server/installation/.md), your authenticated MCP connectors are automatically available alongside Port's native tools. Connector tools are exposed with a prefixed name: `{connector_identifier}_{tool_name}`. For example, a Notion connector with identifier `notion_pms` that exposes `notion-create-view` appears in your IDE as `notion_pms_notion-create-view`. You can see the exact tool names for each connector in the [MCP Servers](https://app.port.io/organization/home?openModal=mcpServers) modal, under the **External MCP** tab. If connector tools appear in Port AI but not in your IDE, work through the troubleshooting steps in the FAQ below. ## Security and permissions[​](#security-and-permissions "Direct link to Security and permissions") * **Per-user authentication**: Each user authenticates with their own account. Data access respects individual permissions in the external tool. * **RBAC enforcement**: Port's role-based access controls apply to all MCP connector access. * **Audit logging**: All tool invocations are logged as part of [AI invocations](/port-ai/ai-invocations/.md). For more details, see [AI Security and Data Controls](/port-ai/security-and-data-controls/.md). ## Limitations[​](#limitations "Direct link to Limitations") * **Self-hosted MCP servers**: Only remote MCP servers are supported. Self-hosted MCP servers cannot be connected as MCP connectors. * **AI agents**: MCP connectors on [AI agent](/agent-management/custom-agents/overview/.md) entities work in interactive chat when the user connects to each server. They do not run in automations or `AI_AGENT` workflow nodes. For automated external MCP, use workflow `type: "AI"` nodes with `mcpServers`. See [use MCP connectors in workflows](/agent-management/ai-registry/mcp-connectors/use-in-workflows/.md), [build an AI agent](/agent-management/custom-agents/build-an-ai-agent/.md), and [external MCP connectors in workflows](/workflows/build-workflows/nodes/action-nodes/ai/.md#external-mcp-connectors-in-workflows). * **Automated workflows**: Workflow `AI` nodes run with an organization automation token. Per-user OAuth connectors are not supported. Use [organization-level OAuth](/agent-management/ai-registry/mcp-connectors/use-in-workflows/.md) or [shared header authentication](/agent-management/ai-registry/mcp-connectors/.md#which-authentication-method-should-i-use-for-mcp-connectors). * **Remote MCP OAuth flows**: OAuth for MCP connectors supports both interactive and headless paths. Per-user and organization-level **authorization code** flows require browser consent. Organization-level **client credentials** lets Port exchange the connector's OAuth Client ID and Client Secret with the upstream MCP server without browser consent, when the upstream server supports that grant. Port stores and refreshes tokens where supported. See [use in workflows](/agent-management/ai-registry/mcp-connectors/use-in-workflows/.md). ## FAQ[​](#faq "Direct link to FAQ") 1What happens if my authentication expires? 2Can AI agents use MCP connectors? 3A user says MCP connectors aren't visible - what should I check? 4Connector tools work in Port AI but not in my IDE - what should I check? 5How do I confirm which Port organization my MCP client is using? 6How are MCP connector tool names formatted? 7Which authentication method should I use for MCP connectors? 8Do I need to provide a token endpoint when connecting to servers like AWS AgentCore? --- # GitHub MCP connector This page covers setting up the GitHub MCP connector. The GitHub MCP connector relies on the general [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md) features - refer to that page for an overview of how MCP connectors work. The GitHub MCP server uses manual OAuth client registration, which means you need to create a GitHub OAuth app before configuring the connector in Port. ## Setup steps[​](#setup-steps "Direct link to Setup steps") ### 1. Create a GitHub OAuth app[​](#1-create-a-github-oauth-app "Direct link to 1. Create a GitHub OAuth app") In your GitHub organization settings, create a new OAuth app with the following fields: * **Application name**: Any recognizable name, e.g. `Port MCP Connector`. * **Homepage URL**: `https://app.port.io` * **Authorization callback URL (EU region)**: `https://api.port.io/v1/mcp/oauth2/callback` US region If your Port organization is on the US region, use `https://api.us.port.io/v1/mcp/oauth2/callback` as the callback URL instead. After registering the app, copy the **Client ID** and generate a **Client Secret** (copy it immediately - it is only shown once). For more information on creating a GitHub OAuth app, see the [GitHub documentation](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app). ### 2. Configure the connector in Port[​](#2-configure-the-connector-in-port "Direct link to 2. Configure the connector in Port") In Port, open the GitHub MCP connector (go to **Connectors** > **MCP Servers** > **GitHub**), expand **Advanced OAuth Settings**, and fill in: * **OAuth Client ID**: The client ID from step 1. * **OAuth Client Secret**: The client secret from step 1. * **OAuth Scope**: `repo,read:org` (minimum required scope). Save and publish the connector. ### 3. Authenticate and use[​](#3-authenticate-and-use "Direct link to 3. Authenticate and use") Follow the standard [MCP connector authentication steps](/agent-management/ai-registry/mcp-connectors/.md#authenticate-to-connectors). Users will be redirected through the GitHub OAuth flow when they first connect. --- # Use in workflows Workflow `type: "AI"` nodes call external tools through [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md). These nodes run with an **organization automation token**, not the triggering user's session, so a per-user OAuth connector is not callable from a workflow. The workflow-usable OAuth paths are **organization-level OAuth**: either one-time admin consent (authorization code) or a fully headless client credentials exchange. This page uses the `atlassian` MCP connector as the running example. You can substitute any OAuth-based MCP connector your admin has registered and published. Prefer shared headers when available If the connector supports API keys, [shared header authentication](/agent-management/ai-registry/mcp-connectors/.md#which-authentication-method-should-i-use-for-mcp-connectors) is usually simpler for automated workflows. Use organization-level OAuth when the connector requires OAuth. We will: * Get an organization access token. * Connect the connector at the organization level (authorization code or client credentials, depending on how the connector is configured). * Validate that the connection is `usable`. * Create a workflow with one `type: "AI"` node scoped to a single connector tool. * Trigger the workflow and verify the tool's response. Which grant type does your connector use? **Client credentials** connectors are connected from the Port UI, once, by an admin. Port stores those tokens at the organization level, so connecting them takes two clicks and no API calls - see [step 2](#client-credentials). **Authorization code** connectors store tokens per user, so connecting as yourself in the UI does not make the connector callable from a workflow. Those connectors need the API flow in [step 2](#authorization-code), run with your organization credentials. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before you start, make sure you have: * A [registered and published](/agent-management/ai-registry/mcp-connectors/.md) MCP connector with at least one tool enabled under **Allowed Tools**. * Admin access in the same Port organization, including the organization's `Client ID` and `Client Secret`. * `curl` and `jq` installed. * The **identifier** of the MCP connector, as set when it was registered. Find it on the **MCP servers** catalog page at `app.port.io//_mcp_servers` in the **Identifier** column. ## Step 1: Set your region and get an organization access token[​](#step-1-set-your-region-and-get-an-organization-access-token "Direct link to Step 1: Set your region and get an organization access token") Set the API base URL for your region, then set your organization credentials and connector identifier. Later commands reuse `$API_BASE`, `$CLIENT_ID`, `$CLIENT_SECRET`, and `$CONNECTOR`. * EU region * US region EU region ``` API_BASE='https://api.port.io' ``` US region ``` API_BASE='https://api.us.port.io' ``` Set your variables ``` CLIENT_ID='' CLIENT_SECRET='' CONNECTOR='atlassian' ``` Replace `` and `` with your organization credentials from the **Credentials** page, and set `CONNECTOR` to your MCP connector identifier if it differs from `atlassian`. Exchange your organization credentials for an access token. This token is organization-level, not personal. Get an organization access token ``` TOKEN=$(curl -s -X POST "$API_BASE/v1/auth/access_token" \ -H 'Content-Type: application/json' \ -d "{\"clientId\":\"$CLIENT_ID\",\"clientSecret\":\"$CLIENT_SECRET\"}" \ | jq -r '.accessToken') ``` You can also generate this token in the Port UI: open the [Credentials](https://app.port.io/organization/home?openModal=credentials) modal, select the **Organization** tab, and click **Generate API token**. See [Create an access token](/api-reference/create-an-access-token/). ## Step 2: Connect at the organization level[​](#step-2-connect-at-the-organization-level "Direct link to Step 2: Connect at the organization level") Follow the option below that matches the connector's **OAuth grant type**. ### Client credentials[​](#client-credentials "Direct link to Client credentials") Connect the connector from the Port UI: 1. Open the [MCP Servers](https://app.port.io/organization/home?openModal=mcpServers) modal and select the **External MCP** tab. 2. Click **Connect** on the connector. There is no consent screen. Port exchanges the connector's OAuth Client ID and Client Secret with the upstream server and stores the resulting tokens for the whole organization, so one admin connecting once is enough for every workflow. ### Authorization code[​](#authorization-code "Direct link to Authorization code") Paste this whole block to get a short-lived session token for the connector and open the upstream provider's consent screen in your browser. Get a session token and open the consent screen ``` SESSION_TOKEN=$(curl -s "$API_BASE/v1/mcp/oauth2/servers/$CONNECTOR/session-token" \ -H 'Accept: application/json' \ -H "Authorization: Bearer $TOKEN" \ | jq -r '.sessionToken') AUTH_URL=$(curl -s -o /dev/null -w '%{redirect_url}' \ "$API_BASE/v1/mcp/oauth2/authenticate?server_id=$CONNECTOR&session_token=$SESSION_TOKEN") if [ -z "$AUTH_URL" ]; then echo "No redirect URL returned. Check that TOKEN and SESSION_TOKEN are valid and not expired." >&2 elif command -v open >/dev/null 2>&1; then open "$AUTH_URL" elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$AUTH_URL" else echo "Open this URL in a browser: $AUTH_URL" fi ``` In the browser, confirm the authorization and approve the permissions for the MCP app. Port then stores the OAuth connection at the organization level. Upstream actions in the external tool are attributed to the account you consent with, not to each workflow triggerer. This consent is needed once; Port refreshes the token where supported. The `server_id` is the Port-side identifier of the registered connector (for example `atlassian`), not the upstream MCP URL. See [Get OAuth2 session token](/api-reference/get-o-auth-2-session-token/) and [Initiate OAuth2 authentication](/api-reference/initiate-o-auth-2-authentication/). ## Step 3: Validate the connection[​](#step-3-validate-the-connection "Direct link to Step 3: Validate the connection") Confirm the connector is connected and callable before wiring it into a workflow. This check applies to the **authorization code** flow above. Client credentials connectors This endpoint reports whether the *calling* identity has connected the connector, so a client credentials connector that an admin connected in the UI can still report `usable: false` here. Its tokens are stored for the organization and the workflow AI node will use them, so verify with the smoke test in [step 5](#step-5-trigger-the-workflow-and-verify) instead. Check the connector is usable ``` curl -s "$API_BASE/v1/mcp/user/servers?includeTools=true" \ -H "Authorization: Bearer $TOKEN" \ | jq --arg id "$CONNECTOR" '.servers[] | select(.identifier==$id) | {identifier, usable, error, toolNames: [.tools[].name]}' ``` Look for `usable: true`, which means the organization-level OAuth connection is live and callable by an AI node. If `error` is populated, recheck steps 1 and 2. The `toolNames` are the prefixed tool names you scope to in the workflow node's `tools` array. MCP tool names follow the `{identifier}_{upstream}` convention, for example `atlassian_atlassianUserInfo`, `atlassian_getJiraIssue`, or `atlassian_searchConfluenceUsingCql`. The prefix is the connector identifier. Use these exact prefixed names in the `tools` array. ## Step 4: Create a workflow AI node that calls the connector[​](#step-4-create-a-workflow-ai-node-that-calls-the-connector "Direct link to Step 4: Create a workflow AI node that calls the connector") We will create the simplest possible workflow: a self-serve trigger feeding one `type: "AI"` node that calls a single Atlassian tool through the `atlassian` connector, with a structured `outputSchema`. Save the workflow definition to a file: **Smoke-test workflow JSON (click to expand)** mcp-atlassian-smoke-test.json ``` { "identifier": "mcp-atlassian-smoke-test", "title": "MCP Atlassian smoke test", "description": "Calls the Atlassian MCP connector from a workflow AI node.", "icon": "Atlassian", "nodes": [ { "identifier": "trigger", "title": "Trigger", "config": { "type": "SELF_SERVE_TRIGGER", "userInputs": { "properties": {} } } }, { "identifier": "ai-call", "title": "Call Atlassian MCP", "config": { "type": "AI", "userPrompt": "Call the atlassian_atlassianUserInfo tool once and return the authenticated user name and email as JSON.", "systemPrompt": "Call atlassian_atlassianUserInfo exactly once, then return the JSON schema immediately. Do not call other tools. Do not loop.", "tools": ["atlassian_atlassianUserInfo"], "mcpServers": [{ "identifier": "atlassian" }], "outputSchema": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string" } }, "required": ["name", "email"] } } } ], "connections": [ { "sourceIdentifier": "trigger", "targetIdentifier": "ai-call" } ] } ``` Create the workflow with the [Create a workflow](/api-reference/create-a-workflow/) API: Create the workflow ``` curl -s -X POST "$API_BASE/v1/workflows" \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d @mcp-atlassian-smoke-test.json | jq ``` Key fields: * `mcpServers: [{ "identifier": "atlassian" }]` references the connector by its Port-side identifier. This must equal `$CONNECTOR` from step 1, so update it if you swapped connectors. * `tools: ["atlassian_atlassianUserInfo"]` scopes the AI node to one tool, using the `{identifier}_{upstream}` naming convention from step 3. Use explicit tool lists in workflows, not defaults. * `systemPrompt` caps the AI to one tool call and forces an immediate return, which prevents runaway loops in automated runs. * `outputSchema` forces a structured JSON response so downstream nodes can parse fields directly. See [external MCP connectors in workflows](/workflows/build-workflows/nodes/action-nodes/ai/.md#external-mcp-connectors-in-workflows) and [MCP servers in API requests](/port-ai/tools-and-approvals/.md#mcp-servers-in-api-requests) for the full node shape and patterns. ## Step 5: Trigger the workflow and verify[​](#step-5-trigger-the-workflow-and-verify "Direct link to Step 5: Trigger the workflow and verify") Trigger a run, then poll it until the status is `COMPLETED` or `FAILED` (about 60 seconds max): Trigger and poll the run ``` RUN_ID=$(curl -s -X POST "$API_BASE/v1/workflows/mcp-atlassian-smoke-test/runs" \ -H "Authorization: Bearer $TOKEN" \ -H 'Content-Type: application/json' \ -d '{}' | jq -r '.workflowRun.identifier') for i in $(seq 1 12); do STATUS=$(curl -s "$API_BASE/v1/workflows/runs/$RUN_ID" -H "Authorization: Bearer $TOKEN" | jq -r '.workflowRun.status') [[ "$STATUS" == "COMPLETED" || "$STATUS" == "FAILED" ]] && break sleep 5 done curl -s "$API_BASE/v1/workflows/runs/$RUN_ID" \ -H "Authorization: Bearer $TOKEN" | jq '.workflowRun.status, .workflowRun.result, .workflowRun.nodeRuns' ``` A successful run returns `status: "COMPLETED"` and `result: "SUCCESS"`, and the `ai-call` node output contains the tool's response. That confirms the AI node reached the connector and returned real data. If the status is `FAILED`, inspect `nodeRuns` for the error. See [Trigger a workflow run](/api-reference/trigger-a-workflow-run/) and [Get a workflow run by identifier](/api-reference/get-a-workflow-run-by-identifier/). ## Next steps[​](#next-steps "Direct link to Next steps") * Swap the smoke-test tool for a real one, such as `atlassian_getJiraIssue` or `atlassian_searchConfluenceUsingCql`, and wire the AI node into a larger workflow with downstream nodes that parse the `outputSchema` fields. * Scope the `tools` array to the minimum set the workflow needs, and keep the `systemPrompt` constrained to prevent loops in automated runs. * If the connector supports API keys, compare this approach with [shared header authentication](/agent-management/ai-registry/mcp-connectors/.md#which-authentication-method-should-i-use-for-mcp-connectors) to pick the right method for automated callers. * If a connector only needs to be callable from interactive Port AI chat (not workflows), use the per-user [authenticate to connectors](/agent-management/ai-registry/mcp-connectors/.md#authenticate-to-connectors) flow instead. --- # MCP registry The MCP registry is an organizational catalog of MCP servers your teams are allowed to use. Platform teams define which servers are approved, developers discover what is available, and new servers enter through a governed request flow instead of ad hoc installs. Port's built-in [`_mcp_server`](/context-lake/data-model/setup-blueprint/default-blueprints/.md#mcp-server) blueprint stores connectors you configure under **Connectors**. An MCP registry blueprint (for example `mcpRegistry`) adds approval status, ownership, installation guidance, and labels so you can answer: *which MCPs are approved in our org?* ![Developer requests an MCP server through a Port action, Port registers it in the software catalog, automation fetches MCP details from GitHub, and the Context Lake makes permitted servers and tools available to AI assistants](/img/ai-agents/MCPRegistry.png) ## What you set up[​](#what-you-set-up "Direct link to What you set up") | Piece | Purpose | | ----------------------------- | ----------------------------------------------------------------------------------------------------------- | | **MCP registry blueprint** | Catalog entity per MCP server with approval status, type, labels, install docs, and optional tool metadata. | | **Request MCP server action** | Self-service form for developers to propose a new server; creates a `pending` registry entry. | | **Catalog views** | Browse and filter approved servers; optional scorecards or dashboards for governance. | For a full step-by-step implementation (blueprint JSON, action JSON, and optional GitHub tool extraction), see the guide [Manage MCP server registry](/guides/all/manage-mcp-registry/.md). ## Create the MCP registry blueprint[​](#create-the-mcp-registry-blueprint "Direct link to Create the MCP registry blueprint") 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Create a blueprint (for example identifier `mcpRegistry`, title **MCP registry**). 3. Add properties that support governance and discovery, for example: * **Approval status** (`approved`, `pending`, `rejected`). * **Server type** (`internal` or `external`). * **Description** and **installation instructions** (markdown). * **Labels** (array) for capabilities such as `database`, `java`, or `filesystem`. * **Repository URL**, **command**, and **endpoint** when relevant. 4. Add relations such as **owning team** and **requested by** (user). Start from the guide JSON The [Manage MCP server registry](/guides/all/manage-mcp-registry/.md) guide includes copy-paste blueprint definitions for `mcpRegistry` and an optional `mcpToolSpecification` blueprint if you sync tools from MCP servers automatically. ## Add a self-service action to request a new MCP[​](#add-a-self-service-action-to-request-a-new-mcp "Direct link to Add a self-service action to request a new MCP") Give developers a **Request new MCP server** action that creates a registry entity in `pending` status. 1. Go to the [self-service](https://app.getport.io/self-serve) page in Port. 2. Create an action with operation **CREATE** on your registry blueprint. 3. Collect inputs such as server name, type, repository URL, description, labels, command, endpoint, and business justification. 4. Map the action to an **upsert entity** invocation that sets `status` to `pending` and relates **requested by** to the triggering user. After submission, platform teams review the entity, update status to `approved` or `rejected`, and enrich installation instructions for approved servers. ![MCP registry entity showing pending approval status after a new server request](/img/guides/mcpRequestImagePendingApproval.png) ![MCP registry entity updated to approved status by the platform team](/img/guides/mcpRequestEntityApproved.png) ## See which MCP servers are approved in your org[​](#see-which-mcp-servers-are-approved-in-your-org "Direct link to See which MCP servers are approved in your org") ### In the catalog[​](#in-the-catalog "Direct link to In the catalog") 1. Open the [MCP registry](https://app.getport.io/mcpServers) page in your portal (or your custom catalog page for the registry blueprint). 2. Filter or sort by **approval status** = `approved`. 3. Open an entity to read installation instructions, labels, available tools, and ownership. ![MCP registry entity detail view showing installation instructions, labels, and ownership](/img/guides/moreMcpDetailsInMCPRegistry.png) ### With Port AI and IDEs[​](#with-port-ai-and-ides "Direct link to With Port AI and IDEs") When the registry lives in your [Context Lake](/context-lake/overview/.md), [Port AI](/port-ai/overview/.md) and the [Port MCP server](/agent-management/port-mcp-server/overview/.md) can answer discovery questions, for example: * Which MCP servers are approved for our team? * Which servers support database operations? * What are the install steps for the approved PostgreSQL MCP? Approved servers configured as [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md) are also available to Port AI through the unified MCP gateway. ## How this fits with Port MCP server[​](#how-this-fits-with-port-mcp-server "Direct link to How this fits with Port MCP server") | Capability | MCP registry (catalog) | Port MCP server | | -------------------------------- | ------------------------------- | ------------------------------------------------------------------------------- | | Govern which servers are allowed | Yes | Uses approved connectors | | Install/config for developers | Documented on registry entities | [Overview and installation](/agent-management/port-mcp-server/installation/.md) | | Query catalog from IDE | Via discovery + connector setup | Native MCP tools | ## Where to go next[​](#where-to-go-next "Direct link to Where to go next") * [Manage MCP server registry](/guides/all/manage-mcp-registry/.md) — full blueprint, action, and optional tool-extraction workflow. * [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md) — connect approved external MCP servers for Port AI. * [Port custom agents](/agent-management/custom-agents/overview/.md) — agents that use registry and connector context at runtime. --- # Prompts [YouTube video player](https://www.youtube.com/embed/ZfOJcl0P31s)
Port MCP Server supports two types of prompts: 1. **Static prompts**: Pre-built prompts that are always available and don't require any setup. 2. **Custom dynamic prompts**: Organization-specific prompts that you create and manage in your Port portal. Both types become available in supported MCP clients (for example, Cursor or Claude) where developers and AI agents can discover and run them with the required inputs. Client Support Required Prompt support in MCP clients depends on whether the client implements the prompts feature according to the [MCP specification](https://modelcontextprotocol.info/docs/clients/). Only some MCP clients currently support prompts. Check the [MCP client documentation](https://modelcontextprotocol.info/docs/clients/) to see if your client supports prompts. ## Static prompts[​](#static-prompts "Direct link to Static prompts") The Port MCP Server comes with pre-built static prompts that are always available in your MCP clients. These prompts provide helpful guidance and assistance for common Port workflows and don't require any setup or configuration. The following static prompt is included: * **troubleshoot\_integration**: Troubleshoot integration issues. This prompt guides you through a systematic approach to identify and fix integration problems by analyzing sync metrics, event logs, and providing fixed configurations. Requires an `integration` argument specifying the name or identifier of the integration to troubleshoot. Static prompts are automatically available in all MCP clients connected to your Port organization and don't appear in your Port portal's Prompts page. ## Custom dynamic prompts[​](#custom-dynamic-prompts "Direct link to Custom dynamic prompts") You can create custom prompts specific to your organization and expose them via the MCP Server. Once defined, these prompts become available in supported MCP clients where developers and AI agents can discover and run them. #### Common use cases[​](#common-use-cases "Direct link to Common use cases") * Automate on-call runbooks and incident-triage guidance * Standardize code review or deployment checklists * Generate structured updates and communications (e.g., incident status, release notes) #### Set up the data model[​](#set-up-the-data-model "Direct link to Set up the data model") 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on `+ Blueprint`. 3. Click on the `{...} Edit JSON` button. 4. Copy and paste the following JSON configuration: **Prompt blueprint JSON (click to expand)** Create in Port ``` { "identifier": "prompt", "title": "Prompt", "icon": "Microservice", "ownership": { "type": "Direct", "title": "Owning Teams" }, "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "arguments": { "items": { "type": "object", "properties": { "name": { "type": "string", "description": "The name of the argument parameter" }, "description": { "type": "string", "description": "A description of what this argument is for" }, "required": { "type": "boolean", "description": "Whether this argument is required or optional", "default": false } }, "required": [ "name", "description" ] }, "type": "array", "title": "Arguments" }, "template": { "icon": "DefaultProperty", "type": "string", "title": "Prompt Template", "format": "markdown" } }, "required": [ "description", "template" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Where prompts appear After this blueprint exists and you create entities for it, prompts will show up in supported MCP clients connected to your Port organization. In clients that surface MCP prompts, you’ll see them listed and ready to run with arguments. #### Create prompts[​](#create-prompts "Direct link to Create prompts") Create entities of the `prompt` blueprint for each prompt you want to expose. At minimum, provide `description` and `template`. Optionally add `arguments` to parameterize the prompt. * Using Prompts page * Using Self-Service Action * Using GitOps 1. Go to the [Prompts page](https://app.port.io/prompts) in your portal. 2. Click **Create prompt**. 3. Fill out the form: * Provide a title and description. * Write the prompt template (supports markdown). * Define any `arguments` (optional) with `name`, `description`, and whether they are `required`. ![Create prompt form in Port](/assets/images/PortPromptForm-4f7a7c7d0e06167a63f4ccc46b609468.png) Template and placeholders The `template` supports markdown and variable placeholders. Each argument defined in `arguments` is exposed by its `name` and can be referenced as `{{name}}` inside the template. When you run the prompt, the MCP Server collects values for required arguments and substitutes them into the matching placeholders before execution. You can create a Self-Service Action in Port to allow your users to create prompts themselves. 1. Go to the [self-service](https://app.port.io/self-serve) page in Port. 2. Click on `+ New Action`. 3. Click on the `{...} Edit JSON` button. 4. Copy and paste the following JSON configuration: **Create New Prompt action JSON (click to expand)** Create in Port ``` { "identifier": "create_new_prompt", "title": "Create New Prompt", "icon": "Microservice", "description": "Create prompt templates that appear in MCP clients (Claude, Cursor, VS Code, etc.) connected to your Port organization. Users can select prompts, provide required arguments, and get contextual AI assistance with dynamic data from Port.", "trigger": { "type": "self-service", "operation": "CREATE", "userInputs": { "properties": { "arguments": { "type": "array", "title": "Template Arguments", "description": "Define arguments that users will provide when running this prompt. Each argument becomes available as {{argument_name}} placeholder in the template. Required arguments must be provided before prompt execution.", "items": { "type": "object", "properties": { "name": { "type": "string", "title": "Argument Name", "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$", "description": "The parameter name that will be substituted in the template using {{name}} syntax (e.g., 'service_name', 'environment', 'incident_id')" }, "description": { "type": "string", "title": "Argument Description", "description": "Clear description explaining what this argument represents and how it's used in the prompt context" }, "is_required": { "type": "boolean", "title": "Is Required", "default": false, "description": "When true, the MCP client (Claude, Cursor, VS Code) will require this argument before executing the prompt" } } } }, "owning_team": { "type": "string", "title": "Owning Team (Optional)", "description": "The team that will own and maintain this prompt template", "format": "entity", "blueprint": "_team" }, "prompt_title": { "type": "string", "title": "Prompt Title", "description": "Human-readable name for this prompt (displayed in MCP clients like Claude, Cursor, and VS Code)", "minLength": 3, "maxLength": 50 }, "prompt_template": { "type": "string", "title": "Prompt Template", "description": "The prompt content with placeholders for dynamic values. Use {{argument_name}} to reference arguments (e.g., 'Analyze service {{service_name}} in {{environment}}'). Supports markdown formatting. The MCP Server substitutes values into {{}} placeholders when the prompt runs.", "minLength": 20, "format": "multi-line" }, "prompt_description": { "type": "string", "title": "Description", "description": "Explain what this prompt does and when to use it. This description helps users select the right prompt from the MCP client interface.", "minLength": 10, "maxLength": 500, "format": "multi-line" } }, "required": [ "prompt_title", "prompt_description", "prompt_template" ], "order": [ "prompt_title", "prompt_description", "prompt_template", "arguments", "owning_team" ], "titles": {} }, "blueprintIdentifier": "prompt" }, "invocationMethod": { "type": "UPSERT_ENTITY", "blueprintIdentifier": "prompt", "mapping": { "identifier": "{{ .inputs.prompt_title | ascii_downcase | gsub(\" \"; \"_\") | gsub(\"[^a-z0-9_]\"; \"\") }}", "title": "{{ .inputs.prompt_title }}", "team": "{{ if (.inputs.owning_team | type) == \"object\" then [.inputs.owning_team.identifier] else [] end }}", "properties": { "template": "{{ .inputs.prompt_template }}", "arguments": "{{ (.inputs.arguments // []) | map({name: .name, description: .description, required: .is_required}) }}", "description": "{{ .inputs.prompt_description }}" } } }, "requiredApproval": false } ``` Developer self-service This Self-Service Action allows developers to create their own prompts without needing direct access to Port's data model. The action validates input and automatically creates properly formatted prompt entities. You can ingest prompts from GitHub repositories using the GitHub integration. This enables a GitOps workflow where GitHub is the source of truth and Port stays in sync automatically. **Prerequisites:** [Port's GitHub integration](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) is installed in your account. **File structure:** Store prompts as YAML files under `.github/prompts` using the `.prompt.yaml` suffix. This is consistent with [GitHub's prompt storage standard](https://docs.github.com/en/github-models/use-github-models/storing-prompts-in-github-repositories). Example file: `.github/prompts/code-review.prompt.yaml` **Example GitHub prompt (click to expand)** ``` name: code-review description: Review code changes for correctness, security, performance, and test coverage following repo conventions. arguments: - name: change_scope description: Summary of the changes or files to review. required: false - name: focus_areas description: Specific concerns to prioritize (e.g., security, performance). required: false message: |- # Code review Review code changes for this repository. Inputs: - change_scope: {{change_scope}} - focus_areas: {{focus_areas}} ## Instructions Focus on: - correctness and edge cases. - security risks and input validation. - performance regressions. - missing tests or insufficient coverage. ``` **Configure the GitHub integration:** 1. Go to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Find your GitHub integration and click on it. 3. Go to the **Mapping** tab. 4. Add the following configuration to your mapping YAML: **GitHub integration configuration (click to expand)** ``` - kind: file selector: query: 'true' files: - path: .github/prompts/*.prompt.yaml skipParsing: false port: entity: mappings: identifier: .repo.name + "/" + .file.name title: .file.name | split(".") | .[0] blueprint: '"prompt"' properties: description: .file.content.description arguments: .file.content.arguments template: .file.content.message ``` 5. Click **Save** to update the integration configuration. After configuration, prompts from your GitHub repositories will automatically sync to Port. Changes in GitHub will appear in your Port catalog after the integration syncs. Learn more For a complete guide on ingesting prompts and skills from GitHub using GitOps, including file structure examples, see the [Ingest prompts and skills from GitHub using GitOps](https://docs.port.io/guides/all/ingest-prompts-skills-from-github-using-gitops/) guide. #### Examples[​](#examples "Direct link to Examples") * Incident triage * Scorecard remediation * On-call handoff summary A prompt to assists on-call engineers by summarizing recent alerts and deploys related to an incident, then suggesting next steps and linking relevant runbooks. **Example prompt execution:** ``` You are assisting with an incident in the payment-service service (production). Incident ID: INC-2024-001 For the last 24 hours: - Summarize critical alerts and recent deploys - Suggest next steps and owners - Link relevant dashboards/runbooks ``` **Incident triage prompt entity JSON (click to expand)** ``` { "identifier": "incident_response_assistant", "title": "Incident Response Assistant", "team": [], "properties": { "description": "Assists with incident response by summarizing critical alerts, recent deploys, and suggesting next steps with relevant dashboards and runbooks", "arguments": [ { "name": "service_name", "required": true, "description": "The name of the service experiencing the incident" }, { "name": "environment", "required": false, "description": "The environment where the incident is occurring (e.g., production, staging)" }, { "name": "incident_id", "required": true, "description": "The unique identifier for the incident" }, { "name": "timeframe", "required": false, "description": "The time period to analyze (e.g., '24 hours', '1 week')" } ], "template": "You are assisting with an incident in the {{service_name}} service ({{environment}}).\nIncident ID: {{incident_id}}\n\nFor the last {{timeframe}}:\n- Summarize critical alerts and recent deploys\n- Suggest next steps and owners\n- Link relevant dashboards/runbooks" }, "relations": {}, "icon": "Microservice" } ``` A prompt that guides engineers to remediate failing scorecard rules by explaining each failure, its impact, and providing step-by-step fixes along with ownership suggestions. **Example prompt execution:** ``` For user-management-service, generate remediation steps for failing rules in the "Production Readiness" scorecard. For each failing rule: - What is failing - Why it matters - Step-by-step remediation - Owners and suggested timeline ``` **Scorecard remediation prompt entity JSON (click to expand)** ``` { "identifier": "scorecard_remediation_guide", "title": "Scorecard Remediation Guide", "team": [], "properties": { "description": "Generate detailed remediation steps for failing scorecard rules, including what's failing, why it matters, step-by-step fixes, and ownership assignments", "arguments": [ { "name": "service_name", "required": true, "description": "The name of the service that needs scorecard remediation" }, { "name": "scorecard_name", "required": true, "description": "The name of the scorecard with failing rules" } ], "template": "For {{service_name}}, generate remediation steps for failing rules in the \"{{scorecard_name}}\" scorecard.\n\nFor each failing rule:\n- What is failing\n- Why it matters\n- Step-by-step remediation\n- Owners and suggested timeline" }, "relations": {}, "icon": "Microservice" } ``` A prompt to generates a thorough on-call handoff report, highlighting active incidents, key risks, pending actions, and upcoming maintenance for the specified team. **Example prompt execution:** ``` Create an on-call handoff for Platform Engineering for the last past week. Include: - Active incidents and current status - Top risks and mitigations - Pending actions and owners - Upcoming maintenance windows ``` **On-Call handoff report prompt entity JSON (click to expand)** ``` { "identifier": "oncall_handoff_report", "title": "On-Call Handoff Report", "team": [], "properties": { "description": "Generate comprehensive on-call handoff documentation including active incidents, risks, pending actions, and upcoming maintenance windows", "arguments": [ { "name": "team", "required": true, "description": "The team name for which to create the on-call handoff" }, { "name": "timeframe", "required": true, "description": "The time period to cover in the handoff (e.g., 'last 24 hours', 'past week')" } ], "template": "Create an on-call handoff for {{team}} for the last {{timeframe}}.\n\nInclude:\n- Active incidents and current status\n- Top risks and mitigations\n- Pending actions and owners\n- Upcoming maintenance windows" }, "relations": {}, "icon": "Microservice" } ``` After creating entities, reconnect or refresh your MCP client; your prompts will be available and will prompt for any defined arguments. #### See prompts in your client[​](#see-prompts-in-your-client "Direct link to See prompts in your client") * Cursor * Claude In Cursor, type **/** to open the prompts list. Selecting a prompt opens an input form for its arguments. ![Prompt list in Cursor](/assets/images/MCPCursorPromptList-985e37aa07b67cc7f0c78d7c8a25c694.png) When you select a prompt, Cursor renders fields for the defined `arguments`. Required ones are marked and must be provided. ![Prompt argument input in Cursor](/assets/images/MCPCursorPromptInput-0a6dfaa04b842a705d29f02ef3ccaf11.png) In Claude, click the **+** button and choose the prompts option to view the list from your Port organization. Selecting a prompt opens a parameter collection flow. ![Prompt list in Claude](/assets/images/MCPClaudePromptList-6aac543d330c111aa31a9ec866a0f327.png) Claude will ask for any required arguments before running the prompt and will substitute them into the template. ![Prompt argument input in Claude](/assets/images/MCPClaudePromptInput-e77fd5318ddb305fd774a8a7d720f6b5.png) ## Using prompts in Port's AI Agent UI[​](#using-prompts-in-ports-ai-agent-ui "Direct link to Using prompts in Port's AI Agent UI") Port's AI Agent web interface now supports MCP prompts, including both static prompts and custom dynamic prompts. You can browse and use prompts directly from the chat input without needing external MCP clients like Cursor or Claude Desktop. ### How it works[​](#how-it-works "Direct link to How it works") 1. **Access prompts**: Click the **+** button in the chat input to open the menu, then select **Prompts**. ![Port AI add menu with Prompts selected](/img/ai-agents/MCPSelectPromptsMenu.png) 2. **Browse and search**: View all available prompts from your Port organization with search and filter support. ![Port AI prompt picker showing search and available prompts](/img/ai-agents/MCPSelectAPrompt.png) 3. **Fill arguments**: When selecting a prompt with arguments (required or optional), a dialog appears to fill in the values. ![Troubleshoot Integration prompt dialog requesting an integration](/img/ai-agents/MCPPromptArgumentsDialog.png) 4. **Use the prompt**: After submitting arguments, the prompt content is attached to your message and sent to the AI agent. ![Port AI chat with the troubleshoot\_integration prompt attached](/img/ai-agents/MCPPromptSelected.png) Argument handling When you select a prompt with arguments (required or optional), an arguments dialog appears. When you select a prompt with no arguments, Port uses it immediately without any additional steps. --- # Enrich Port AI with skills Skills give [Port AI](/port-ai/overview/.md) domain-specific instructions at runtime. Instead of repeating the same prompts or hoping the model infers your process, you package expert workflows as skills that Port AI loads when a task matches. This page covers how skills enrich Port AI during a conversation or agent run. For skill concepts, blueprints, and creation steps, see the [Skills overview](/agent-management/ai-registry/skills/overview/.md). ## When to use skills[​](#when-to-use-skills "Direct link to When to use skills") Use skills when you want Port AI to follow a repeatable process, for example: * Troubleshooting integration sync issues with a fixed diagnostic sequence. * Guiding deployment decisions against your organization's approval rules. * Running incident root-cause analysis with consistent data-gathering steps. Skills work across [Port AI Assistant](/port-ai/port-ai-assistant/.md), [AI agents](/agent-management/custom-agents/overview/.md), the [Port MCP Server](/agent-management/port-mcp-server/overview/.md), and the [AI Agent widget](/interface-builder/port-interface/dashboards/custom-widgets/.md#ai-agent). ## How Port AI loads skills[​](#how-port-ai-loads-skills "Direct link to How Port AI loads skills") Port AI exposes a `load_skill` tool through the Port MCP Server. When your prompt or agent task matches a skill's description, Port AI loads that skill before continuing. ``` load_skill({ name: "troubleshoot-integration" }) ``` You can also load a specific reference file from a skill: ``` load_skill({ name: "troubleshoot-integration", resource: "references/common-errors.md" }) ``` Each skill includes a name, description (when to use it), step-by-step instructions, and optional references and assets. Skills follow the open [Agent Skills specification](https://agentskills.io/specification). ## Built-in skills[​](#built-in-skills "Direct link to Built-in skills") Port ships built-in skills for common platform workflows. These require no setup and are available to all users. The `troubleshoot-integration` skill helps diagnose integration sync issues, mapping errors, and missing entities. It walks Port AI through identifying the integration, reviewing sync metrics and event logs, and recommending fixes in the correct format. Built-in skill reference See the full list and behavior details in the [Skills overview](/agent-management/ai-registry/skills/overview/.md#built-in-skills). ## Custom skills in Port AI[​](#custom-skills-in-port-ai "Direct link to Custom skills in Port AI") You can create custom skills as entities in your software catalog. Custom skills override built-in skills when they share the same name, so your organization's process takes precedence. To create a custom skill: 1. Define the `skill` blueprint in your [data model](https://app.getport.io/settings/data-model). 2. Create skill entities with instructions, references, and assets. 3. Ask Port AI or an agent to perform a task that matches the skill description. For blueprint JSON, entity examples, and GitOps ingestion options, see [Creating custom skills](/agent-management/ai-registry/skills/overview/.md#creating-custom-skills) in the Skills overview. ## Attach skills to agents[​](#attach-skills-to-agents "Direct link to Attach skills to agents") When you [build an AI agent](/agent-management/custom-agents/build-an-ai-agent/.md), you can configure which skills and tools the agent can access. Domain-specific agents combine catalog context with the right skills so automated runs follow your standards without manual prompting. ## Next steps[​](#next-steps "Direct link to Next steps") * [Skills overview](/agent-management/ai-registry/skills/overview/.md): full reference for skill structure, creation, and best practices. * [Skills registry](/agent-management/ai-registry/skills/skills-registry/.md): organize approved skills across your organization. * [Skills usage analytics](/agent-management/ai-registry/skills/skills-usage-analytics/.md): measure skill adoption from Cursor and Claude. * [Port MCP Server](/agent-management/port-mcp-server/overview/.md): use skills from your IDE or external MCP clients. --- # Skills Open Beta This feature is currently in open beta and available to all organizations. Should you encounter any bugs or functionality issues, please let us know so we can rectify them as soon as possible. Your feedback is greatly appreciated! ⭐ We are actively iterating to improve the experience. Breaking changes in the data model and functionality may occur. We will communicate changes on an ongoing basis. General availability is expected by Q4 2026. Skills are domain-specific guidance packages that help Port AI handle specialized tasks more effectively. When the AI encounters a task that matches a skill's description, it loads the skill's step-by-step instructions to ensure consistent, thorough handling. ## What are skills[​](#what-are-skills "Direct link to What are skills") Skills provide structured workflows for common but complex tasks. Instead of relying on general AI capabilities, skills encode expert knowledge into reusable instruction sets that guide the AI through specific processes. Each skill contains: * **Name**: A unique identifier for the skill. * **Description**: When the AI should use this skill. * **Instructions**: Step-by-step guidance in markdown format. * **References** (optional): Additional documentation or context. * **Assets** (optional): Templates, configuration files, or other resources. Skills follow the open [Agent Skills specification](https://agentskills.io/specification). ## Example skills you can create[​](#example-skills-you-can-create "Direct link to Example skills you can create") * Deployment guidelines * Engineering metrics * Analyze incident root cause ## Deployment guidelines A skill that helps decide on a deployment strategy, which workflows to run, how to check deployment status, and get approval. This skill can guide developers through your organization's deployment process by providing step-by-step instructions on choosing the right deployment strategy for different environments, running the appropriate CI/CD workflows, monitoring deployment progress, and obtaining necessary approvals from stakeholders before deploying to production. ## Engineering metrics A skill that structures how you want to get engineering metrics and what to look for. This skill helps analyze key performance indicators like lead time for changes, deployment frequency, change failure rate, and AI usage and cost metrics. It can provide guidance on how to collect, aggregate, and interpret these metrics to gain insights into your team's performance and identify areas for improvement. ## Analyze incident root cause A skill that provides a walkthrough on steps to run when analyzing an incident root cause. This skill guides you through a systematic process for investigating incidents, including gathering relevant logs and metrics, identifying the timeline of events, examining related services and dependencies, analyzing error patterns, and documenting findings to prevent similar incidents in the future. ## How skills work[​](#how-skills-work "Direct link to How skills work") Skills are loaded via the `load_skill` tool available in the [Port MCP Server](/agent-management/port-mcp-server/overview/.md). When Port AI detects a task that matches a skill's description, it automatically loads the relevant skill before proceeding. **Loading a skill:** ``` load_skill({ name: "troubleshoot-integration" }) ``` **Loading a specific resource from a skill:** ``` load_skill({ name: "troubleshoot-integration", resource: "references/common-errors.md" }) ``` ## Built-in skills[​](#built-in-skills "Direct link to Built-in skills") Port includes built-in skills for common workflows. These skills are available to all users without any configuration. **`action-permissions-helper` (click to expand)** Helps you configure permissions for self-service actions, with a focus on dynamic permission policies.
Use it to write or debug JQ expressions for execute and approve policies, or to understand why an existing permission policy isn't behaving as expected. **`dashboard-page-helper` (click to expand)** Guides the creation of dashboard pages with widgets, including a fallback approach for large pages with five or more widgets. It also checks your existing sidebar structure before placing a new page, keeping your navigation organized. **`ei-ai-copilot-adoption` (click to expand)** Sets up GitHub Copilot adoption metrics and the GitHub Copilot AI Adoption dashboard as part of Engineering Intelligence.
Use it to measure or visualize license utilization, acceptance rate, lines-of-code efficiency, low-engagement users, or per-team Copilot rollups. **`ei-delivery-performance` (click to expand)** Sets up pull request and merge request delivery performance metrics and dashboards for GitHub, GitLab, or Azure DevOps as part of Engineering Intelligence.
Use it to measure cycle time, throughput, stale pull requests, or reviewer coverage. **`ei-foundation` (click to expand)** Validates whether your organization has the data model required for Engineering Intelligence use cases, before you run the Delivery Performance or AI Copilot Adoption skills.
Use it whenever Engineering Intelligence widgets, scorecards, or dashboards appear empty, to check readiness and identify what's missing. **`mapping-assistant` (click to expand)** Helps you create, modify, or troubleshoot integration mapping configurations.
Use it to write JQ expressions for data transformation, understand test results and errors, or debug mapping issues. **`skill-creator` (click to expand)** Guides you through creating and registering custom AI skills in Port, from clarifying the skill's purpose to writing effective instructions and registering it as a Port entity. **`troubleshoot-integration` (click to expand)** Diagnoses and resolves integration sync issues, mapping errors, and data problems.
Use it when data isn't syncing or appearing in Port, entities are missing or incorrect, or you're seeing sync errors and mapping configuration problems. **`troubleshoot-scim` (click to expand)** Diagnoses and resolves SCIM provisioning issues, including group provisioning errors, missing users, compatibility issues, and login problems. Works with Azure Entra ID, Okta, JumpCloud, Google Workspace, or any SCIM 2.0-compatible identity provider. **`workflows` (click to expand)** Creates or updates Port workflows, including triggers, action nodes, conditions, and data flow.
Use it to add, update, or remove individual nodes, connections, or user inputs in a workflow. ## Creating custom skills[​](#creating-custom-skills "Direct link to Creating custom skills") [YouTube video player](https://www.youtube.com/embed/h42j9WnzTBI) You can create custom skills tailored to your organization's specific workflows. Custom skills are stored as entities in Port and can override built-in skills with the same name. ### Step 1: Create the skill blueprint[​](#step-1-create-the-skill-blueprint "Direct link to Step 1: Create the skill blueprint") First, create the `skill` blueprint in your Port organization. Go to your [data model](https://app.port.io/settings/data-model) and create a new blueprint with the following configuration: **Skill blueprint (click to expand)** Create in Port ``` { "identifier": "skill", "title": "Skill", "icon": "Learn", "schema": { "properties": { "description": { "title": "Description", "type": "string", "description": "What the skill does and when the model should use it" }, "instructions": { "title": "Instructions", "type": "string", "format": "markdown", "description": "Step-by-step instructions for the AI to follow" }, "references": { "title": "References", "type": "array", "description": "Reference documents for the skill", "items": { "type": "object", "properties": { "path": { "type": "string", "description": "Resource path (e.g., references/common-errors.md)" }, "content": { "type": "string", "description": "The file content" }, "description": { "type": "string", "description": "Optional description of the resource" } }, "required": ["path", "content"], "additionalProperties": false } }, "assets": { "title": "Assets", "type": "array", "description": "Asset files (templates, configs) for the skill", "items": { "type": "object", "properties": { "path": { "type": "string", "description": "Asset path (e.g., assets/mapping-template.yaml)" }, "content": { "type": "string", "description": "The file content" }, "description": { "type": "string", "description": "Optional description of the asset" } }, "required": ["path", "content"], "additionalProperties": false } }, "location": { "type": "string", "title": "Location", "description": "The target directory where the skill will be created on the machine.", "default": "global", "enum": [ "global", "project" ], "enumColors": { "global": "lightGray", "project": "lightGray" } } }, "required": ["description", "instructions", "location"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` ### Step 2: Create skill entities[​](#step-2-create-skill-entities "Direct link to Step 2: Create skill entities") Once the blueprint exists, create entities for each custom skill. Each entity represents one skill. * Manual creation * Using GitOps **Required properties:** | Property | Description | | -------------- | --------------------------------------------------------------------------------------------------------------------------- | | `identifier` | Unique skill name using lowercase letters and hyphens (e.g., `deploy-service`). | | `description` | A clear description of what the skill does and when Port AI should use it. This helps the AI decide when to load the skill. | | `instructions` | Step-by-step instructions in markdown format. | **Optional properties:** | Property | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------- | | `references` | Array of reference documents. Each item needs a `path` (e.g., `references/runbook.md`) and `content` (the document text). | | `assets` | Array of asset files like templates or configs. Same structure as references: `path` and `content`. | ### Example: Custom deployment skill Here's an example of a custom skill for deploying services in your organization: ``` { "identifier": "deploy-to-production", "title": "Deploy to Production", "properties": { "description": "Guide for deploying services to production. Use when users ask to deploy, release, or promote a service to production.", "instructions": "# Deploy to Production\n\nFollow these steps to deploy a service to production:\n\n## Step 1: Verify prerequisites\n\n- Check that all tests pass.\n- Verify the service has a production-readiness scorecard score above 80%.\n- Confirm the service owner has approved the deployment.\n\n## Step 2: Run the deployment\n\nExecute the deployment action for the target service and environment.\n\n**Example input:**\n- Service: `payment-service`\n- Environment: `production`\n\n**Expected output:**\n- Deployment initiated successfully.\n- Action run ID returned for tracking.\n\n## Step 3: Verify deployment\n\n- Check the action run status.\n- Verify the service is healthy in production.\n- Monitor for any alerts in the first 15 minutes.\n\n## Common edge cases\n\n- If tests are failing, do not proceed with deployment.\n- If scorecard score is below threshold, recommend remediation steps first.\n- If deployment fails, check logs and suggest rollback if needed.", "references": [ { "path": "references/deployment-runbook.md", "content": "# Deployment Runbook\n\n## Pre-deployment checklist\n\n- [ ] All CI checks pass\n- [ ] Code review approved\n- [ ] QA sign-off received\n\n## Rollback procedure\n\nIf deployment fails:\n1. Revert to previous version\n2. Notify on-call team\n3. Create incident ticket" }, { "path": "references/common-errors.md", "content": "# Common Deployment Errors\n\n## ImagePullBackOff\nCause: Container registry authentication failed.\nFix: Verify registry credentials.\n\n## CrashLoopBackOff\nCause: Application fails to start.\nFix: Check application logs and configuration." } ], "assets": [ { "path": "assets/deployment-config.yaml", "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: {{ service_name }}\nspec:\n replicas: 3\n strategy:\n type: RollingUpdate" } ] } } ``` You can ingest skills from GitHub repositories using the GitHub integration. This enables a GitOps workflow where GitHub is the source of truth and Port stays in sync automatically. **Prerequisites:** [GitHub Ocean integration](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean) is installed in your account. Choose one mapping kind. Do not combine them: * Use the **`skill`** kind when you only need `SKILL.md` instructions in Port. It does not ingest files under `references/` or `assets/`. * Use the **`file`** kind when those extra files must land on the skill entity as `properties.references` and `properties.assets`. Do not model extra files as related entities, and do not reconstruct `references` with mirror or calculation properties. `port skills add` and `port skills sync` only read stored `properties.references` and `properties.assets` on the skill entity. **Recommended file structure:** Store each skill in its own folder under `skills/`. Put instructions in `SKILL.md`. Put any other files the skill needs under `references/` or `assets/`: ``` skills/ ├── deploy-to-production/ │ ├── SKILL.md │ ├── references/ │ │ └── deployment-runbook.md │ └── assets/ │ └── deployment-config.yaml └── troubleshoot-integration/ └── SKILL.md ``` **Configure the GitHub integration:** 1. Go to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Find your GitHub integration and click **Manage**. 3. Go to the **Mapping** tab. 4. Add the mapping for the kind you chose: * Skill kind * File kind The `skill` kind discovers `SKILL.md` files, parses frontmatter, and maps instructions. Extra files in the skill folder are not ingested. **GitHub Ocean skill kind mapping (click to expand)** ``` deleteDependentEntities: false createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: skill selector: query: 'true' paths: - path: 'skills/**/SKILL.md' organization: my-org # Optional if githubOrganization is set (required if not set) repos: - name: my-skills-repo branch: main port: entity: mappings: identifier: .__repository.full_name + "/" + .skill.skillMdPath title: .skill.name // .skill.skillMdPath blueprint: '"skill"' properties: instructions: .skill.instructions description: .skill.description location: '"global"' ``` The `file` kind maps `SKILL.md` plus files under `references/` and `assets/` onto one skill entity. Use the same identifier for every file in a skill folder, and set `enableMergeEntity: true` so extra files merge onto the entity instead of replacing the instructions. **GitHub Ocean file kind mapping (click to expand)** ``` deleteDependentEntities: false createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: file selector: query: 'true' files: - path: skills/**/SKILL.md organization: my-org # Optional if githubOrganization is set (required if not set) repos: - name: my-skills-repo branch: main port: entity: mappings: identifier: .repository.name + "/" + (.path | split("/")[1]) title: .path | split("/")[1] blueprint: '"skill"' properties: instructions: .content description: .content | capture("description:\\s*(?.+)").d location: '"global"' relations: {} - kind: file selector: query: 'true' files: - path: skills/**/references/** skipParsing: true organization: my-org # Optional if githubOrganization is set (required if not set) repos: - name: my-skills-repo branch: main port: entity: mappings: identifier: .repository.name + "/" + (.path | split("/")[1]) blueprint: '"skill"' properties: references: - path: .path | split("/") | .[2:] | join("/") content: .content - kind: file selector: query: 'true' files: - path: skills/**/assets/** skipParsing: true organization: my-org # Optional if githubOrganization is set (required if not set) repos: - name: my-skills-repo branch: main port: entity: mappings: identifier: .repository.name + "/" + (.path | split("/")[1]) blueprint: '"skill"' properties: assets: - path: .path | split("/") | .[2:] | join("/") content: .content ``` 5. Click **Save & Resync** to update the integration configuration. After configuration, skills from your GitHub repositories will automatically sync to Port. Changes in GitHub will appear in your Port catalog after the integration syncs. #### Common practices * Namespace the `identifier` so skills with the same folder name in different repos don't collide. The skill kind example uses `.__repository.full_name`. The file kind example uses `.repository.name + "/" + ...`. * Keep `deleteDependentEntities: false` on this connector. Setting it to `true` means a temporary sync issue, like a rate limit, a revoked permission, or a renamed repo, deletes skill entities on the next resync instead of leaving them stale. * Extract `description` from the SKILL.md frontmatter instead of mapping the raw file content, so the catalog shows a short summary rather than the entire file. * With the file kind, each extra-file event replaces the whole `references` or `assets` array. If a skill has more than one file in the same directory, use the [upload skills from a folder](/agent-management/ai-registry/skills/overview/.md#upload-skills-from-a-folder) script so the full array is written in one request. Using GitHub (Sunset) or also syncing prompts If your account still uses Port's older GitHub app integration, or you also want to sync prompt files alongside skills, see the [Ingest prompts and skills from GitHub using GitOps](/guides/all/ingest-prompts-skills-from-github-using-gitops/.md) guide, which covers both integration versions and the combined prompts and skills setup. ## Upload skills from a folder[​](#upload-skills-from-a-folder "Direct link to Upload skills from a folder") You can manage skills as folders in your repository and upload them to Port using a script. This is useful for version-controlling your skills and integrating with CI/CD pipelines. ### Skill folder structure[​](#skill-folder-structure "Direct link to Skill folder structure") Organize each skill as a folder with the following structure: ``` my-skill/ ├── SKILL.md # Skill definition (required) ├── references/ # Reference documents (optional) │ ├── runbook.md │ └── common-errors.md └── assets/ # Asset files (optional) └── config-template.yaml ``` The `SKILL.md` file contains YAML frontmatter with the skill metadata, followed by the instructions in markdown: ``` --- name: deploy-to-production description: Guide for deploying services to production. Use when users ask to deploy, release, or promote a service to production. metadata: title: Deploy to Production --- # Deploy to Production Follow these steps to deploy a service to production: ## Step 1: Verify prerequisites - Check that all tests pass. - Verify the service has a production-readiness scorecard score above 80%. ## Step 2: Run the deployment Execute the deployment action for the target service. ## Common edge cases - If tests are failing, do not proceed with deployment. - If deployment fails, check logs and suggest rollback. ``` ### Upload script[​](#upload-script "Direct link to Upload script") Use the following script to read a skill folder and upload it to Port as an entity. **Upload script (click to expand)** **Prerequisites:** ``` # Python: install dependencies pip install requests pyyaml # JavaScript: install dependencies npm install js-yaml # Bash: install jq and yq, make script executable # macOS: brew install jq yq # Ubuntu: sudo apt-get install jq && pip install yq chmod +x ./upload-skill.sh ``` **Usage:** ``` # Set your Port credentials export PORT_CLIENT_ID="your-client-id" export PORT_CLIENT_SECRET="your-client-secret" # For US region, also set: # export PORT_BASE_URL="https://api.us.port.io" # Test with dry-run first (no changes made) python upload_skill.py ./my-skill --dry-run # Python node upload-skill.js ./my-skill --dry-run # JavaScript ./upload-skill.sh ./my-skill --dry-run # Bash # Upload the skill to Port python upload_skill.py ./my-skill # Python node upload-skill.js ./my-skill # JavaScript ./upload-skill.sh ./my-skill # Bash ``` * Python * JavaScript * Bash ``` #!/usr/bin/env python3 """ Upload a skill folder to Port as an entity. Usage: python upload_skill.py [--dry-run] Environment variables: PORT_CLIENT_ID - Port client ID PORT_CLIENT_SECRET - Port client secret PORT_BASE_URL - Port API base URL (default: https://api.port.io) """ import os import sys import json import requests import yaml import re from pathlib import Path PORT_BASE_URL = os.environ.get('PORT_BASE_URL', 'https://api.port.io') def get_port_token(): client_id = os.environ.get('PORT_CLIENT_ID') client_secret = os.environ.get('PORT_CLIENT_SECRET') if not client_id or not client_secret: raise Exception('PORT_CLIENT_ID and PORT_CLIENT_SECRET environment variables are required') response = requests.post( f'{PORT_BASE_URL}/v1/auth/access_token', json={'clientId': client_id, 'clientSecret': client_secret} ) response.raise_for_status() return response.json()['accessToken'] def parse_skill_file(skill_dir): skill_path = Path(skill_dir) / 'SKILL.md' if not skill_path.exists(): raise Exception(f'SKILL.md not found in {skill_dir}') content = skill_path.read_text() # Parse YAML frontmatter match = re.match(r'^---\n(.*?)\n---\n(.*)$', content, re.DOTALL) if not match: raise Exception('Invalid SKILL.md format: missing YAML frontmatter') frontmatter = yaml.safe_load(match.group(1)) instructions = match.group(2).strip() if not frontmatter.get('name'): raise Exception('SKILL.md frontmatter must have a "name" field') if not frontmatter.get('description'): raise Exception('SKILL.md frontmatter must have a "description" field') return { 'name': frontmatter['name'], 'description': frontmatter['description'], 'metadata': frontmatter.get('metadata', {}), 'instructions': instructions } def read_resources_from_dir(skill_dir, sub_dir): dir_path = Path(skill_dir) / sub_dir if not dir_path.exists(): return [] resources = [] for file_path in dir_path.rglob('*'): if file_path.is_file(): relative_path = f'{sub_dir}/{file_path.relative_to(dir_path)}' resources.append({ 'path': relative_path, 'content': file_path.read_text() }) return resources def upload_skill(token, entity): response = requests.post( f'{PORT_BASE_URL}/v1/blueprints/skill/entities?upsert=true&merge=true', headers={ 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' }, json=entity ) response.raise_for_status() return response.json() def main(): args = sys.argv[1:] dry_run = '--dry-run' in args skill_dir = next((arg for arg in args if not arg.startswith('--')), None) if not skill_dir: print('Usage: python upload_skill.py [--dry-run]') sys.exit(1) skill_dir = Path(skill_dir).resolve() if not skill_dir.exists(): print(f'Error: Directory not found: {skill_dir}') sys.exit(1) print(f'Reading skill from: {skill_dir}') # Parse SKILL.md skill = parse_skill_file(skill_dir) print(f' Name: {skill["name"]}') print(f' Description: {skill["description"][:50]}...') # Read references and assets references = read_resources_from_dir(skill_dir, 'references') assets = read_resources_from_dir(skill_dir, 'assets') print(f' References: {len(references)} file(s)') print(f' Assets: {len(assets)} file(s)') # Build entity entity = { 'identifier': skill['name'], 'title': skill['metadata'].get('title', skill['name']), 'properties': { 'description': skill['description'], 'instructions': skill['instructions'], 'references': references if references else None, 'assets': assets if assets else None } } print('\nEntity to create/update:') print(json.dumps(entity, indent=2)) if dry_run: print('\n[DRY RUN] Would create/update entity in Port') else: print('\nAuthenticating with Port...') token = get_port_token() result = upload_skill(token, entity) print(f'\n✅ Entity created/updated successfully!') print(f' Identifier: {result["entity"]["identifier"]}') print(f' Title: {result["entity"]["title"]}') if __name__ == '__main__': main() ``` ``` #!/usr/bin/env node /** * Upload a skill folder to Port as an entity. * * Usage: * node upload-skill.js [--dry-run] * * Environment variables: * PORT_CLIENT_ID - Port client ID * PORT_CLIENT_SECRET - Port client secret * PORT_BASE_URL - Port API base URL (default: https://api.port.io) */ const fs = require('fs'); const path = require('path'); const yaml = require('js-yaml'); const PORT_BASE_URL = process.env.PORT_BASE_URL || 'https://api.port.io'; async function getPortToken() { const clientId = process.env.PORT_CLIENT_ID; const clientSecret = process.env.PORT_CLIENT_SECRET; if (!clientId || !clientSecret) { throw new Error('PORT_CLIENT_ID and PORT_CLIENT_SECRET environment variables are required'); } const response = await fetch(`${PORT_BASE_URL}/v1/auth/access_token`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientId, clientSecret }) }); if (!response.ok) { throw new Error(`Failed to get Port token: ${response.status} ${response.statusText}`); } const data = await response.json(); return data.accessToken; } function parseSkillFile(skillDir) { const skillPath = path.join(skillDir, 'SKILL.md'); if (!fs.existsSync(skillPath)) { throw new Error(`SKILL.md not found in ${skillDir}`); } const content = fs.readFileSync(skillPath, 'utf-8'); // Parse YAML frontmatter const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); if (!frontmatterMatch) { throw new Error('Invalid SKILL.md format: missing YAML frontmatter'); } const frontmatter = yaml.load(frontmatterMatch[1]); const instructions = frontmatterMatch[2].trim(); if (!frontmatter.name) { throw new Error('SKILL.md frontmatter must have a "name" field'); } if (!frontmatter.description) { throw new Error('SKILL.md frontmatter must have a "description" field'); } return { name: frontmatter.name, description: frontmatter.description, metadata: frontmatter.metadata || {}, instructions }; } function readResourcesFromDir(skillDir, subDir) { const dirPath = path.join(skillDir, subDir); if (!fs.existsSync(dirPath)) { return []; } const resources = []; const files = fs.readdirSync(dirPath, { recursive: true }); for (const file of files) { const filePath = path.join(dirPath, file); const stat = fs.statSync(filePath); if (stat.isFile()) { const relativePath = `${subDir}/${file}`; const content = fs.readFileSync(filePath, 'utf-8'); resources.push({ path: relativePath, content }); } } return resources; } async function uploadSkill(token, entity) { const response = await fetch(`${PORT_BASE_URL}/v1/blueprints/skill/entities?upsert=true&merge=true`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify(entity) }); if (!response.ok) { const errorBody = await response.text(); throw new Error(`Failed to create entity: ${response.status} ${response.statusText}\n${errorBody}`); } return response.json(); } async function main() { const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); const skillDir = args.find(arg => !arg.startsWith('--')); if (!skillDir) { console.error('Usage: node upload-skill.js [--dry-run]'); process.exit(1); } const resolvedDir = path.resolve(skillDir); if (!fs.existsSync(resolvedDir)) { console.error(`Error: Directory not found: ${resolvedDir}`); process.exit(1); } console.log(`Reading skill from: ${resolvedDir}`); // Parse SKILL.md const skill = parseSkillFile(resolvedDir); console.log(` Name: ${skill.name}`); console.log(` Description: ${skill.description.substring(0, 50)}...`); // Read references and assets const references = readResourcesFromDir(resolvedDir, 'references'); const assets = readResourcesFromDir(resolvedDir, 'assets'); console.log(` References: ${references.length} file(s)`); console.log(` Assets: ${assets.length} file(s)`); // Build entity const entity = { identifier: skill.name, title: skill.metadata.title || skill.name, properties: { description: skill.description, instructions: skill.instructions, references: references.length > 0 ? references : undefined, assets: assets.length > 0 ? assets : undefined } }; console.log('\nEntity to create/update:'); console.log(JSON.stringify(entity, null, 2)); if (dryRun) { console.log('\n[DRY RUN] Would create/update entity in Port'); } else { console.log('\nAuthenticating with Port...'); const token = await getPortToken(); const result = await uploadSkill(token, entity); console.log('\n✅ Entity created/updated successfully!'); console.log(` Identifier: ${result.entity.identifier}`); console.log(` Title: ${result.entity.title}`); } } main().catch(err => { console.error('Error:', err.message); process.exit(1); }); ``` ``` #!/bin/bash # # Upload a skill folder to Port as an entity. # # Usage: # ./upload-skill.sh [--dry-run] # # Environment variables: # PORT_CLIENT_ID - Port client ID # PORT_CLIENT_SECRET - Port client secret # PORT_BASE_URL - Port API base URL (default: https://api.port.io) # # Dependencies: jq, yq set -e PORT_BASE_URL="${PORT_BASE_URL:-https://api.port.io}" # Parse arguments DRY_RUN=false SKILL_DIR="" for arg in "$@"; do if [[ "$arg" == "--dry-run" ]]; then DRY_RUN=true elif [[ ! "$arg" == --* ]]; then SKILL_DIR="$arg" fi done if [[ -z "$SKILL_DIR" ]]; then echo "Usage: ./upload-skill.sh [--dry-run]" exit 1 fi SKILL_DIR=$(cd "$SKILL_DIR" && pwd) if [[ ! -d "$SKILL_DIR" ]]; then echo "Error: Directory not found: $SKILL_DIR" exit 1 fi echo "Reading skill from: $SKILL_DIR" # Check for SKILL.md SKILL_FILE="$SKILL_DIR/SKILL.md" if [[ ! -f "$SKILL_FILE" ]]; then echo "Error: SKILL.md not found in $SKILL_DIR" exit 1 fi # Parse SKILL.md frontmatter using yq FRONTMATTER=$(sed -n '/^---$/,/^---$/p' "$SKILL_FILE" | sed '1d;$d') SKILL_NAME=$(echo "$FRONTMATTER" | yq -r '.name') SKILL_DESCRIPTION=$(echo "$FRONTMATTER" | yq -r '.description') SKILL_TITLE=$(echo "$FRONTMATTER" | yq -r '.metadata.title // .name') # Get instructions (everything after the closing --- of frontmatter, trimmed) INSTRUCTIONS=$(awk '/^---$/{n++; next} n>=2' "$SKILL_FILE" | sed '/./,$!d') echo " Name: $SKILL_NAME" echo " Description: ${SKILL_DESCRIPTION:0:50}..." # Read references REFERENCES="[]" if [[ -d "$SKILL_DIR/references" ]]; then REFERENCES=$(find "$SKILL_DIR/references" -type f | while read -r file; do REL_PATH="references/$(basename "$file")" CONTENT=$(cat "$file" | jq -Rs .) echo "{\"path\": \"$REL_PATH\", \"content\": $CONTENT}" done | jq -s '.') echo " References: $(echo "$REFERENCES" | jq 'length') file(s)" else echo " References: 0 file(s)" fi # Read assets ASSETS="[]" if [[ -d "$SKILL_DIR/assets" ]]; then ASSETS=$(find "$SKILL_DIR/assets" -type f | while read -r file; do REL_PATH="assets/$(basename "$file")" CONTENT=$(cat "$file" | jq -Rs .) echo "{\"path\": \"$REL_PATH\", \"content\": $CONTENT}" done | jq -s '.') echo " Assets: $(echo "$ASSETS" | jq 'length') file(s)" else echo " Assets: 0 file(s)" fi # Build entity JSON ENTITY=$(jq -n \ --arg identifier "$SKILL_NAME" \ --arg title "$SKILL_TITLE" \ --arg description "$SKILL_DESCRIPTION" \ --arg instructions "$INSTRUCTIONS" \ --argjson references "$REFERENCES" \ --argjson assets "$ASSETS" \ '{ identifier: $identifier, title: $title, properties: { description: $description, instructions: $instructions, references: (if ($references | length) > 0 then $references else null end), assets: (if ($assets | length) > 0 then $assets else null end) } }') echo "" echo "Entity to create/update:" echo "$ENTITY" | jq . if [[ "$DRY_RUN" == "true" ]]; then echo "" echo "[DRY RUN] Would create/update entity in Port" exit 0 fi # Get Port token if [[ -z "$PORT_CLIENT_ID" || -z "$PORT_CLIENT_SECRET" ]]; then echo "Error: PORT_CLIENT_ID and PORT_CLIENT_SECRET environment variables are required" exit 1 fi echo "" echo "Authenticating with Port..." ACCESS_TOKEN=$(curl --silent --location --request POST "${PORT_BASE_URL}/v1/auth/access_token" \ --header 'Content-Type: application/json' \ --data-raw "{ \"clientId\": \"${PORT_CLIENT_ID}\", \"clientSecret\": \"${PORT_CLIENT_SECRET}\" }" | jq -r '.accessToken') # Upload entity RESULT=$(curl --silent --location --request POST "${PORT_BASE_URL}/v1/blueprints/skill/entities?upsert=true&merge=true" \ --header "Authorization: Bearer ${ACCESS_TOKEN}" \ --header 'Content-Type: application/json' \ --data-raw "$ENTITY") echo "" echo "✅ Entity created/updated successfully!" echo " Identifier: $(echo "$RESULT" | jq -r '.entity.identifier')" echo " Title: $(echo "$RESULT" | jq -r '.entity.title')" ``` Find your Port credentials Your Port credentials are available in the [Port UI](https://app.port.io) under **Settings > Credentials**. ## Priority and overriding[​](#priority-and-overriding "Direct link to Priority and overriding") Custom skills take priority over built-in skills. If you create a custom skill with the same identifier as a built-in skill (e.g., `troubleshoot-integration`), your custom skill will be used instead. This allows you to: * Customize built-in workflows for your organization's specific needs. * Add organization-specific context to existing skills. * Replace generic instructions with your team's best practices. ## Best practices[​](#best-practices "Direct link to Best practices") When creating custom skills, follow these guidelines for best results: ### Keep instructions concise Skills are loaded into the AI's context, so keep instructions under 5000 tokens (approximately 500 lines). Move detailed reference content to the `references` array instead of putting everything in the main instructions. ### Write clear trigger descriptions The `description` field determines when Port AI loads the skill. Write descriptions that clearly indicate: * What task the skill handles. * Keywords or phrases users might use. * When NOT to use the skill (if relevant). **Good example:** > "Diagnose and resolve integration sync issues, mapping errors, and data problems. Use when users report missing data, sync failures, or integration errors." **Poor example:** > "Helps with integrations." ### Structure instructions effectively Organize instructions with clear structure. Include: * Step-by-step instructions for completing the task. * Examples of inputs and outputs. * Common edge cases and how to handle them. ### Use resources for detailed content Put detailed documentation, error catalogs, or templates in the `references` and `assets` arrays. The AI can load these on demand when needed, keeping the main instructions focused. ## Related pages[​](#related-pages "Direct link to Related pages") * [Skills registry](/agent-management/ai-registry/skills/skills-registry/.md): publish and sync approved skills from Git. * [Enrich Port AI with skills](/agent-management/ai-registry/skills/enrich-port-ai-with-skills/.md): load skills at runtime in Port AI. * [Skills usage analytics](/agent-management/ai-registry/skills/skills-usage-analytics/.md): measure skill adoption from Cursor and Claude. --- # Skills registry A skills registry is a central library of reusable skills that everyone across the organization can publish and consume. Instead of each squad maintaining its own prompts and instructions, approved skills live in one catalog in Port, discovered and installed through the UI, Port MCP, or a CLI command. ## What the registry solves[​](#what-the-registry-solves "Direct link to What the registry solves") Engineering teams adopt AI agents at different layers of the stack. Without a registry, skills get duplicated, quality varies, and platform teams lose visibility into what instructions agents follow. The skills registry gives you: * **A catalog of every skill in your org** - discoverable through the UI, Port MCP, or a CLI install. * **A golden path for sharing skills** - one standard way to propose, review, and publish a new skill across the org, instead of ad hoc pull requests. * **Composable skills** - group related skills into installable plugins as your registry grows. * **Quality and governance bars** - certification review, duplicate detection, and an ROI scorecard catch skills that shouldn't be in the registry, whether they're low-quality, redundant, or not worth their cost. * **Visibility into usage and cost** - real usage and spend data connected to the registry, so you can see which skills are actually adopted, and which are worth what they cost to run. ## Ingest skills from Git[​](#ingest-skills-from-git "Direct link to Ingest skills from Git") You can manage skills as `SKILL.md` files in Git and ingest them into Port through the GitHub Ocean or GitLab v2 integrations. ### Choose the skill or file kind[​](#choose-the-skill-or-file-kind "Direct link to Choose the skill or file kind") Choose one mapping kind. Do not combine them: * Use the **`skill`** kind when you only need `SKILL.md` instructions in Port. It does not ingest files under `references/` or `assets/`. * Use the **`file`** kind when those extra files must land on the skill entity as `properties.references` and `properties.assets`. Do not model extra files as related entities, and do not reconstruct `references` with mirror or calculation properties. * Skill kind * File kind The `skill` kind discovers common Agent Skill layouts (for example `.cursor/skills/**/SKILL.md` and `skills/**/SKILL.md`), parses frontmatter and instructions, and emits one entity per skill. **GitHub Ocean mapping configuration (click to expand)** ``` deleteDependentEntities: false createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: skill selector: query: 'true' paths: - path: 'skills/**/SKILL.md' organization: my-org repos: - name: my-repo branch: main - path: '.cursor/skills/**/SKILL.md' organization: my-org port: entity: mappings: identifier: .__repository.full_name + "/" + .skill.skillMdPath title: .skill.name // .skill.skillMdPath blueprint: '"skill"' properties: instructions: .skill.instructions description: .skill.description location: '"global"' ``` **GitLab v2 mapping configuration (click to expand)** ``` deleteDependentEntities: false createMissingRelatedEntities: true resources: - kind: skill selector: query: 'true' paths: - path: 'skills/**/SKILL.md' repos: - group/my-repo - path: '.cursor/skills/**/SKILL.md' port: entity: mappings: identifier: .repo.path_with_namespace + "/" + .skill.skillMdPath title: .skill.name // .skill.skillMdPath blueprint: '"skill"' properties: instructions: .skill.instructions description: .skill.description location: '"global"' ``` On GitLab, skill discovery always uses the repository tree API (glob-friendly, not Advanced Search). On GitHub, discovery uses recursive git-tree matching with the same path-selector shape as the file kind - large orgs pay a tree walk per scanned repository. See also: * [GitHub Ocean - ingest agent skills](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-agent-skills) * [GitLab v2 - ingest agent skills](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#ingest-agent-skills) The `file` kind is the original GitOps pattern for skills. Map `SKILL.md` into `instructions`, and map files under `references/` and `assets/` into the matching array properties on the **same skill identifier**. Set `enableMergeEntity: true` so extra files merge onto the skill instead of replacing the instructions. **GitHub Ocean file kind mapping (click to expand)** ``` deleteDependentEntities: false createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: file selector: query: 'true' files: - path: skills/**/SKILL.md organization: my-org repos: - name: my-repo branch: main port: entity: mappings: identifier: .repository.name + "/" + (.path | split("/")[1]) title: .path | split("/")[1] blueprint: '"skill"' properties: instructions: .content description: .content | capture("description:\\s*(?.+)").d location: '"global"' relations: {} - kind: file selector: query: 'true' files: - path: skills/**/references/** skipParsing: true organization: my-org repos: - name: my-repo branch: main port: entity: mappings: identifier: .repository.name + "/" + (.path | split("/")[1]) blueprint: '"skill"' properties: references: - path: .path | split("/") | .[2:] | join("/") content: .content - kind: file selector: query: 'true' files: - path: skills/**/assets/** skipParsing: true organization: my-org repos: - name: my-repo branch: main port: entity: mappings: identifier: .repository.name + "/" + (.path | split("/")[1]) blueprint: '"skill"' properties: assets: - path: .path | split("/") | .[2:] | join("/") content: .content ``` Each extra-file event replaces the whole `references` or `assets` array. If a skill has more than one file in the same directory, use the [upload skills from a folder](/agent-management/ai-registry/skills/overview/.md#upload-skills-from-a-folder) script so the full array is written in one request. ### Alternative: folder kind + included files[​](#alternative-folder-kind--included-files "Direct link to Alternative: folder kind + included files") You can still manage skills as folders in Git and ingest them with `kind: folder` when you need a custom folder layout. Folder kind with `includedFiles: [SKILL.md]` maps instructions only. If you also need `references/` or `assets/`, use the file kind instead of adding extra mappings on top of folder kind. **GitHub Ocean folder-based mapping (click to expand)** ``` deleteDependentEntities: false createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: folder selector: query: 'true' folders: - path: '**/skills/*' organization: my-org # Optional if githubOrganization is set (required if not set) repos: - name: my-repo branch: main includedFiles: - SKILL.md port: entity: mappings: identifier: .__repository.name + "-" + (.folder.path | split("/") | last) title: .__repository.name + "-" + (.folder.path | split("/") | last) blueprint: '"skill"' properties: instructions: .__includedFiles["SKILL.md"] description: .folder.path | split("/") | last location: '"global"' ``` Using GitHub (Sunset) or also syncing prompts If your account still uses Port's older GitHub app integration, or you also want to sync prompt files alongside skills, see the [Ingest prompts and skills from GitHub using GitOps](https://docs.port.io/guides/all/ingest-prompts-skills-from-github-using-gitops/) guide, which covers both integration versions and the combined prompts and skills setup. After configuration, skills from your repositories will automatically sync to Port. Changes in Git will appear in your Port catalog after the integration syncs. Common practices * Keep `deleteDependentEntities: false` on this connector. Setting it to `true` means a temporary sync issue, like a rate limit, a revoked permission, or a renamed repo, deletes skill entities on the next resync instead of leaving them stale. * Use a consistent folder naming convention in `skills/` so skill identifiers are predictable across repositories. You can also [upload skills from a folder](/agent-management/ai-registry/skills/overview/.md#upload-skills-from-a-folder) with a script for CI/CD pipelines. ## Build a skills registry with our guides[​](#build-a-skills-registry-with-our-guides "Direct link to Build a skills registry with our guides") Follow these guides in order to go from a bare Git repository to a fully governed skills registry: 1. [Set up a skills registry](/guides/all/setup-skills-registry/.md) - model every skill in your org as a Port entity, assign it to groups, and let developers discover them. 2. [Ship new skills through one golden path](/guides/all/ship-skills-through-one-golden-path/.md) - give developers one standard way to propose a skill and track the review from a single dashboard. 3. [Certify skills to meet industry and org standards](/guides/all/certify-skills-to-meet-industry-and-org-standards/.md) - add an AI-driven review on top of the deterministic scorecards. 4. [Avoid duplicate skills in your org registry](/guides/all/avoid-duplicate-skills-in-your-org-registry/.md) - catch a duplicate skill pre-publish or during certification. 5. [Bundle skills into plugins](/guides/all/bundle-skills-into-plugins-in-your-org-registry/.md) - group related skills into installable plugins, with an AI-driven suggestion and a self-service workflow to apply it. 6. [Measure the ROI of skills in your org registry](/guides/all/measure-the-roi-of-skills-in-your-org-registry/.md) - connect real Claude usage and spend data to the registry. 7. [Visualize Claude Skills adoption and usage](/guides/all/visualize-claude-skills-adoption/.md) - build a dashboard tracking adoption, sharing, and spend. ## Next steps[​](#next-steps "Direct link to Next steps") * [Skills overview](/agent-management/ai-registry/skills/overview/.md): blueprint setup, entity creation, and best practices. * [Enrich Port AI with skills](/agent-management/ai-registry/skills/enrich-port-ai-with-skills/.md): how Port AI loads skills at runtime. * [Skills usage analytics](/agent-management/ai-registry/skills/skills-usage-analytics/.md): measure skill adoption from Cursor and Claude. * [Build an AI agent](/agent-management/custom-agents/build-an-ai-agent/.md): attach sanctioned skills to domain-specific agents. * [External agents](/agent-management/external-agents/overview/.md): inventory of agents hosted on external platforms. --- # Skills usage analytics Publishing skills in a [skills registry](/agent-management/ai-registry/skills/skills-registry/.md) is only half of the story. Platform teams also need to see which skills engineers actually use, how often they are invoked, and where adoption is growing or stalling. Port ingests skill usage metrics from AI coding tools into your [software catalog](/context-lake/data-model/configure-data-model/.md), so you can build dashboards, scorecards, and reports on skill adoption alongside the rest of your engineering context. ## What you can analyze[​](#what-you-can-analyze "Direct link to What you can analyze") Depending on the integration, skill usage data can help you answer questions such as: * Which skills are used most often across the organization. * How skill adoption changes day over day. * Which users and teams invoke each skill. * Which surfaces drive usage (for example chat, Claude Code, or Office agents). * How estimated spend or attributed usage value relates to specific skills. Use this data to prioritize which skills to govern in the registry, retire unused skills, and prove the impact of platform-owned skill packs. ## Ingest skill usage metrics[​](#ingest-skill-usage-metrics "Direct link to Ingest skill usage metrics") Choose the integration that matches the AI tools your teams use. Each integration creates skill usage blueprints and mapping out of the box when you enable the relevant resource kinds. | Method | Best for | Skill usage resource | Setup | | -------------------------- | -------------------------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | **Cursor** | Teams using Cursor Analytics (Enterprise) for skill adoption | `cursor-team-skill-usage` | [Cursor metrics](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/cursor/.md) | | **Claude AI (Enterprise)** | Teams using Claude Enterprise analytics for org, user, or team skill usage | `claude-ai-skill-usage` | [Claude AI metrics](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/claude/.md) | Enterprise analytics access Both skill usage kinds rely on vendor analytics APIs that require an Enterprise plan and an API key with analytics read permissions. Confirm access on the Cursor or Claude integration page before enabling these kinds. ### Cursor[​](#cursor "Direct link to Cursor") The Cursor integration syncs daily team-level skill adoption from the Cursor Analytics API. Each entity represents a skill on a given day, including skill name and usage count. See the [`cursor-team-skill-usage`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/cursor/.md) kind for blueprint schema, default mapping, sample payload, and optional user filters. ### Claude AI (Enterprise)[​](#claude-ai-enterprise "Direct link to Claude AI (Enterprise)") The Claude integration syncs skill usage for a single day at a time from Anthropic's skill analytics API. Each entity includes invocation counts, distinct users, share status, product-surface breakdowns, and optional spend fields. Leave `groupBy` empty for org-level totals, or set it to one of `user_id`, `rbac_group_id`, or `product` to break the same metrics out by user, team, or product surface. Use one dimension at a time. See the [`claude-ai-skill-usage`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/claude/.md) kind under Claude AI (Enterprise) mode for blueprint schema and default mapping. For user- and team-level rows, follow [Breaking down analytics on user and team level](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/claude/.md#breaking-down-analytics-on-user-and-team-level). ## Related pages[​](#related-pages "Direct link to Related pages") * [Skills overview](/agent-management/ai-registry/skills/overview/.md): skill concepts, blueprints, and creation. * [Skills registry](/agent-management/ai-registry/skills/skills-registry/.md): publish and sync approved skills from Git. * [Enrich Port AI with skills](/agent-management/ai-registry/skills/enrich-port-ai-with-skills/.md): load skills at runtime in Port AI. * [Visualize Cursor metrics](/guides/all/visualize-cursor-metrics/.md): dashboards for Cursor adoption and impact. * [Visualize Claude metrics](/guides/all/visualize-claude-metrics/.md): dashboards for Claude usage and cost. --- # Build agents Built on Port AI AI Agents are built on top of [Port AI](/port-ai/overview/.md) and leverage its underlying capabilities for data access, security, and execution. This guide focuses on agent-specific building techniques. ## Getting started[​](#getting-started "Direct link to Getting started") Building an AI agent in Port involves several key steps.
Let's walk through the process of creating an agent that can assist your developers with finding information and completing tasks. ## Create a new AI agent[​](#create-a-new-ai-agent "Direct link to Create a new AI agent") To create a new agent, head to the AI Agents catalog page (this page will be created for you when you [activate the feature](/agent-management/custom-agents/overview/.md#access-to-the-feature)). Click on the "New AI Agent" button and fill the form with the agent details. ![AI Agents catalog with execution modes](/img/ai-agents/AIAgentsList.png) ![Register AI Agent modal form fields](/img/ai-agents/AIAgentsAddAgentModal.png) We recommend following the steps below. ### Step 1: Define your agent's purpose[​](#step-1-define-your-agents-purpose "Direct link to Step 1: Define your agent's purpose") The first step in building an AI agent is deciding on its purpose.
Here we will determine whether the agent will: * Answer questions about your development environment. * Help run actions and complete tasks. We recommend starting with a simple use case that a junior assistant with access to your data could assist with. For inspiration, check our [AI agents guides](/guides/.md?tags=Agent%20management). ### Step 2: Configure data access tools[​](#step-2-configure-data-access-tools "Direct link to Step 2: Configure data access tools") Configure which data tools your agent can access. Configure access to Port data through [Port AI tools](/port-ai/overview/.md#tools). Available tools include data query tools for retrieving information from your catalog, as well as running self-service actions. ### Step 3: Configure actions access[​](#step-3-configure-actions-access "Direct link to Step 3: Configure actions access") Actions are executed through the `run_action` tool, which dynamically handles all available actions in your Port instance. When you configure an action for the agent, it becomes available as a tool the agent can use. Provide action access in one of these ways: 1. **Allowed Actions input (recommended)** - Add the specific actions this agent can execute in the **Allowed Actions** input in the AI agent form. 2. **Advanced mode (regex)** - Switch to **Advanced mode (regex)** if you need full control over the `tools` list (for example, mixed custom patterns). In this mode, you can add action tools using patterns such as `run_{action_identifier}`. 3. Choose the execution mode (automatic or approval-required) for the selected actions. To find an action identifier, open the [self-service](https://app.port.io/self-serve) page and copy the action identifier. While the underlying implementation uses the unified `run_action` tool, you can still reference specific actions with `run_{action_identifier}` for clarity and backward compatibility. For details on manual approval vs automatic execution patterns, see [Port AI tool execution modes](/port-ai/tools-and-approvals/.md#tool-execution-modes). ### Step 4: Configure MCP server access[​](#step-4-configure-mcp-server-access "Direct link to Step 4: Configure MCP server access") You can give your agent access to tools from external systems through [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md). This lets the agent query or act on data that does not live in your Port catalog (for example, Notion, Linear, or Slack). MCP connectors configured on the agent entity work in interactive chat when the user connects to each server. They do not run in automations or `AI_AGENT` workflow nodes. For workflow external MCP, see [use MCP connectors in workflows](/agent-management/ai-registry/mcp-connectors/use-in-workflows/.md). 1. In the **MCP servers** relation field, select which MCP servers this agent can use. 2. In the **Allowed MCP tools** section, choose which tools the agent may use for each selected server. You can toggle individual tools, or use **All** / **None** for a server. 3. **Save** is available only when every selected server is connected and has at least one tool enabled. ![AI Agent MCP servers and tool toggles](/img/ai-agents/AIAgentAllowedMcpTools.png) For connector setup and authentication, see [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md). ### Step 5: Define the prompt[​](#step-5-define-the-prompt "Direct link to Step 5: Define the prompt") The prompt is your main tool for influencing your agent's success. You will likely iterate on it as you observe how the agent responds to different queries. For comprehensive guidance on crafting effective prompts, see [Context Engineering](/port-ai/context-engineering/.md). This covers prompt design principles, examples, and best practices for creating prompts that leverage your organization's specific data and terminology. ![Edit Prompt dialog with markdown editor preview](/img/ai-agents/AIAgentPrompt.png) ### Step 6: Activate your agent[​](#step-6-activate-your-agent "Direct link to Step 6: Activate your agent") When you feel your agent is ready: 1. Set its status to "Active". 2. Start interacting with it through the [available interfaces](/agent-management/custom-agents/interact-with-ai-agents/.md). ### Step 7: Configure user permissions[​](#step-7-configure-user-permissions "Direct link to Step 7: Configure user permissions") Creating and activating an agent is not enough for your users to interact with it. Access to Port AI features is controlled separately through the `_ai_invocation` blueprint's permissions. Permissions required Users need the **Read**, **Register**, and **Update** permissions on the `_ai_invocation` blueprint to invoke and interact with any Port AI feature, including your newly created agent. [Learn how to configure access to Port AI →](/port-ai/security-and-data-controls/.md#controlling-access-to-port-ai) ## Evaluating your agent performance[​](#evaluating-your-agent-performance "Direct link to Evaluating your agent performance") Continuous evaluation and improvement are essential for maintaining effective AI agents. We recommend implementing a regular review process: 1. **Weekly reviews**: Set aside time each week to review agent interactions 2. **Identify patterns**: Look for recurring issues or misunderstandings in how the agent interprets queries 3. **Refine the prompt**: Update your agent's prompt based on your findings to address common issues For details on how to analyze agent behavior and view execution plans, see [AI invocations](/port-ai/ai-invocations/.md). ## Examples[​](#examples "Direct link to Examples") For comprehensive examples of AI agents in action, see our practical implementation guides: **Infrastructure & Operations:** * [Heal unhealthy Kubernetes pods](/guides/all/heal-unhealthy-k8s-pods/.md) - Automated pod healing and recovery * [AI-powered incident management](/guides/all/ai-powered-incident-management/.md) - AI-powered incident communication and updates **Development Workflow:** * [Automatically resolve tickets with Port](/guides/all/automatically-resolve-tickets/.md?approach=coding-agents) - AI-powered code generation and ticket resolution. * [PR Enricher AI Agent](/guides/all/setup-pr-enricher-ai-agent/.md) - Enhanced pull request analysis and context **Platform Operations:** * [Platform Request Triage AI Agent](/guides/all/setup-platform-request-triage-ai-agent/.md) - Intelligent request routing and prioritization * [Service Explorer AI Agent](/guides/all/setup-service-explorer-ai-agent/.md) - Service discovery and exploration These guides provide step-by-step implementation details, including tool configuration, prompt engineering, and real-world integration patterns. ## Formatting the agent response[​](#formatting-the-agent-response "Direct link to Formatting the agent response") To format the agent's response, you can specify the desired format in its prompt. For optimal results when using the UI, it's recommended to request a markdown format response. This allows for better presentation and readability of the information provided by the agent. When sending messages through Slack, our [Slack app](/interface-builder/notifications/slack-app/.md) convert the markdown format into a Slack compatible formatting. ### Example of a Markdown Response[​](#example-of-a-markdown-response "Direct link to Example of a Markdown Response") ``` :rocket: *New version deployed!* [Added logs](https://www.example.com) From [john-123](https://github.com/john-123) ``` ## Troubleshooting and FAQ[​](#troubleshooting-and-faq "Direct link to Troubleshooting and FAQ") 1I don't see an option to add an AI agent 2My agent can't access the data I need - what's wrong? 3How can I make sure the agent doesn't run actions without approval? 4My agent isn't performing well - how do I improve it? 5Users can see the agent but get an error when invoking it 6My agent is active but doesn't appear in the AI chat for my users 7I configured actions for my agent but it won't execute them --- # Interact with agents Built on Port AI AI Agents are specialized implementations built on top of [Port AI](/port-ai/overview/.md), designed for machine-to-machine communication and autonomous operations within defined domains. ## Getting started[​](#getting-started "Direct link to Getting started") Once you've built your AI agents, you can integrate them into your workflows and systems. AI agents are designed for machine-to-machine communication and can be triggered programmatically through Port's API or automatically through workflow automations. MCP servers require a connected user If an agent uses [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md), each user must connect to all of the agent's MCP servers before chatting with it. Agent MCP does not run in automations or `AI_AGENT` workflow nodes. For workflow external MCP, see [use MCP connectors in workflows](/agent-management/ai-registry/mcp-connectors/use-in-workflows/.md). See [authenticate to connectors](/agent-management/ai-registry/mcp-connectors/.md#authenticate-to-connectors). ## How AI Agents Work[​](#how-ai-agents-work "Direct link to How AI Agents Work") AI Agents are domain-specific entities built on [Port AI](/port-ai/overview/.md) with configured tools and prompts. For details on agent architecture and configuration, see [AI agents overview](/agent-management/custom-agents/overview/.md) and [Build agents](/agent-management/custom-agents/build-an-ai-agent/.md). ## Interaction approaches[​](#interaction-approaches "Direct link to Interaction approaches") AI agents are designed for specific domains and use cases. When interacting with agents, you target the specific agent that has the expertise and tools needed for your task. This approach works best for structured scenarios like automation triggers and API integrations where you know which domain-specific capabilities you need. ## Interaction methods[​](#interaction-methods "Direct link to Interaction methods") * AI Chat Widget * API integration * Actions and automations You can add AI agents directly to your dashboards as interactive widgets, providing easy access to their capabilities right where you need them. Follow these steps to add an AI agent widget: 1. Go to a dashboard. 2. Click on `+ Widget`. 3. Select the `AI Chat` widget. 4. Configure your widget settings: * **Agent**: Select the specific AI agent to use * **Title**: Set a descriptive name for the widget * **Description**: Add a description for the widget 5. Save your dashboard configuration. 6. Start asking questions through the chat interface. ![New widget menu with AI agent option](/img/ai-agents/AIAgentWidgetMenu.png)![Add AI agent widget dialog fields](/img/ai-agents/AIAgentsTaskManagerWidget.png) The widget provides a chat interface where you can ask questions and receive responses from the **selected agent** without leaving your dashboard. The widget will inherit all the agent's configuration including the prompts, conversation starters, tool access, etc. **Context Awareness**: The widget recognizes the page it's placed on and suggests relevant context for your question, such as the full page, or specific elements on it like a widget, graph, text block, or table. Click a suggestion to add it to your question, then ask about what you're looking at without needing to spell out the details yourself. Click it again to remove it if it's not relevant to your question. Conversation starters appear in the initial state, helping users understand what they can ask the agent. Users can either click a starter to begin a new chat or type their own question. ![AI agent widget conversation starter prompts](/img/ai-agents/AIAgentsWidgetConversationStarters.png) AI agents can be integrated into your applications and workflows through Port's API. Agents use the same streaming API patterns as Port AI. **Agent-specific endpoint:** ``` curl 'https://api.port.io/v1/agent//invoke?stream=true' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ --data-raw '{"prompt":"Analyze the health of our production services"}' ``` For comprehensive details on API interaction, streaming responses, and event types, see [API interaction](/port-ai/api-interaction/.md). For quota management, see [Limits and quotas](/port-ai/limits-and-quotas/.md). You can trigger AI agents through Port's actions and automations, enabling integration of AI capabilities into your existing workflows. This is the primary method for creating autonomous, machine-to-machine AI systems. ### Automation Workflows Automations allow you to automatically trigger AI agents based on events in your Port catalog. This enables reactive AI systems that respond to changes in your infrastructure or applications. **Example: Automatic Incident Response (click to expand)** When a new incident is created, automatically trigger an agent that: * Analyzes the incident context * Gathers relevant information from related services * Creates initial response documentation * Notifies appropriate teams Create in Port ``` { "identifier": "incident_response_automation", "title": "Automatic Incident Response", "trigger": { "type": "automation", "event": { "type": "ENTITY_CREATED", "blueprintIdentifier": "incident" } }, "invocationMethod": { "type": "WEBHOOK", "url": "https://api.port.io/v1/agent/incident_response_agent/invoke", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "prompt": "New incident created: {{ .entity.title }}. Severity: {{ .entity.properties.severity }}. Please analyze and provide initial response recommendations.", "labels": { "source": "automation", "incident_id": "{{ .entity.identifier }}", "trigger_type": "incident_created" } } } } ``` **Example: Infrastructure Healing (click to expand)** Monitor infrastructure health and automatically trigger healing agents when issues are detected: Create in Port ``` { "identifier": "k8s_healing_automation", "title": "Kubernetes Workload Healing", "trigger": { "type": "automation", "event": { "type": "ENTITY_UPDATED", "blueprintIdentifier": "k8s_workload" }, "condition": { "type": "JQ", "expressions": [ ".diff.before.properties.isHealthy == \"Healthy\"", ".diff.after.properties.isHealthy == \"Unhealthy\"" ], "combinator": "and" } }, "invocationMethod": { "type": "WEBHOOK", "url": "https://api.port.io/v1/agent/k8s_healing_agent/invoke", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "prompt": "Workload {{ .event.diff.after.title }} is unhealthy. Current state: {{ .event.diff.after.properties.replicas }} replicas, {{ .event.diff.after.properties.readyReplicas }} ready. Please analyze and fix.", "labels": { "source": "automation", "workload_name": "{{ .event.diff.after.identifier }}", "namespace": "{{ .event.diff.after.properties.namespace }}" } } } } ``` ### Self-Service Actions You can create self-service actions that invoke AI agents, allowing users to trigger intelligent workflows on-demand. **Example: Service Analysis Action (click to expand)** Create in Port ``` { "identifier": "analyze_service_health", "title": "Analyze Service Health", "description": "Get AI-powered analysis of service health and recommendations", "trigger": { "type": "self-service", "operation": "DAY-2", "blueprintIdentifier": "service" }, "invocationMethod": { "type": "WEBHOOK", "url": "https://api.port.io/v1/agent/service_health_agent/invoke", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "prompt": "Analyze the health of service {{ .entity.title }}. Check metrics, recent deployments, and incidents.", "labels": { "source": "self_service", "service_name": "{{ .entity.identifier }}", "requested_by": "{{ .trigger.by.user.email }}" } } } } ``` For comprehensive examples, see our [AI guides](/guides/.md?tags=Agent%20management). ## Discovering Available Agents[​](#discovering-available-agents "Direct link to Discovering Available Agents") You can discover available AI agents through the AI Agents catalog page or programmatically via the API using the `_ai_agent` blueprint. **cURL Example (click to expand)** ``` curl -L 'https://api.port.io/v1/blueprints/_ai_agent/entities' \ -H 'Accept: application/json' \ -H 'Authorization: Bearer ' ``` ## AI interaction details[​](#ai-interaction-details "Direct link to AI interaction details") Every AI agent interaction creates an entity in Port, providing tracking and analysis capabilities. For comprehensive details on AI invocations, execution plans, and tools used, see [AI invocations](/port-ai/ai-invocations/.md). ### Data & security considerations[​](#data--security-considerations "Direct link to Data & security considerations") For information on data handling and security, see [AI Security and Data Controls](/port-ai/security-and-data-controls/.md). ## Limits[​](#limits "Direct link to Limits") AI agents use the same rate limits and quotas as Port AI. *Note: These limits apply when using Port's managed AI infrastructure. [Bring your own LLM provider](/port-ai/llm-providers-management/overview/.md) to use your provider's limits instead.* For detailed information on limits, monitoring, and quota management, see [Limits and quotas](/port-ai/limits-and-quotas/.md). ## Troubleshooting and FAQ[​](#troubleshooting-and-faq "Direct link to Troubleshooting and FAQ") 1Why can't I send messages to an agent in chat? 2What determines whether an agent can answer a question? 3How do I improve agent performance? --- # Port custom agents Built on Port AI AI Agents are specialized implementations built on top of [Port AI](/port-ai/overview/.md), designed for specific domains and machine-to-machine communication. For general AI capabilities and human interaction, see [Port AI](/port-ai/overview/.md). [YouTube video player](https://www.youtube.com/embed/wtnT7F8xsWQ) ## What are Port AI Agents?[​](#what-are-port-ai-agents "Direct link to What are Port AI Agents?") Port AI Agents are pre-configured, domain-specific entities built on [Port AI](/port-ai/overview/.md) that enable intelligent machine-to-machine communication. Unlike general AI assistants designed for human interaction, AI Agents are purpose-built for specific domains (like incident management, deployment orchestration, or security monitoring) and excel at autonomous operations within defined boundaries. ![AI Agents catalog with example automation agents](/img/ai-agents/AIAgentsList.png) ## Agent Characteristics[​](#agent-characteristics "Direct link to Agent Characteristics") AI Agents are distinguished by three key characteristics that make them ideal for machine-to-machine workflows: 1. **Domain Ownership** - Each agent is specifically designed for a particular domain or function (incident management, security monitoring, deployment orchestration). 2. **Autonomous Operation** - Agents operate independently within defined boundaries , making them perfect for automated workflows, background monitoring and alerting, and system-to-system communication where human intervention isn't required. 3. **Reusability Across Systems** - Agents can be integrated into multiple systems and workflows, like API-driven integration, third-party system integrations, etc. ## When to Use AI Agents vs Port AI[​](#when-to-use-ai-agents-vs-port-ai "Direct link to When to Use AI Agents vs Port AI") Choose **AI Agents** when you need: * **Machine-to-machine communication** for automated workflows. * **Domain-specific expertise** (e.g., incident response, security analysis). * **Autonomous operations** that run without human intervention. * **Reusable logic** that can be embedded in multiple systems. Choose **[Port AI](/port-ai/overview/.md)** when you need: * **Human interaction** and conversational interfaces. * **General-purpose queries** across your entire catalog. * **Ad-hoc exploration** and discovery. ## Agent-Specific Use Cases[​](#agent-specific-use-cases "Direct link to Agent-Specific Use Cases") AI Agents excel at machine-to-machine scenarios that require domain expertise and autonomous operation: * Kubernetes Healing Agent * Incident Response Agent * Security Compliance Agent This agent automatically monitors and heals Kubernetes infrastructure by detecting failures, analyzing resource constraints, and triggering remediation workflows through monitoring system integrations. **Tools this agent would have:** * Kubernetes cluster monitoring and diagnostics. * Resource utilization analysis tools. * Pod restart and scaling automation. * Alert correlation and notification systems. **Example scenarios:** * Automatically detects pod failures and restart issues. * Analyzes resource constraints and suggests scaling actions. * Integrates with monitoring systems to trigger remediation workflows. This agent streamlines incident management by monitoring service health, creating automated incident reports, and reducing alert noise through intelligent correlation while keeping stakeholders informed throughout outages. **Tools this agent would have:** * Service health monitoring and alerting. * Incident creation and management tools. * Multi-system alert correlation engines. * Stakeholder notification and communication systems. **Example scenarios:** * Monitors service health and automatically creates incident reports. * Correlates alerts across multiple systems to reduce noise. * Provides automated status updates to stakeholders during outages. This agent continuously maintains security posture by scanning for vulnerabilities and misconfigurations, creating prioritized remediation tickets, and preventing vulnerable code from reaching production through CI/CD integration. **Tools this agent would have:** * Vulnerability scanning and assessment tools. * Security misconfiguration detection systems. * Automated ticket creation and prioritization. * CI/CD pipeline integration and gates. **Example scenarios:** * Continuously scans for security vulnerabilities and misconfigurations. * Automatically creates remediation tickets with context and priority. * Integrates with CI/CD pipelines to prevent deployment of vulnerable code. ## Getting Started with AI Agents[​](#getting-started-with-ai-agents "Direct link to Getting Started with AI Agents") AI Agents are designed for organizations that need autonomous, domain-specific AI capabilities. Before applying, ensure you have: * Experience with [Port AI](/port-ai/overview/.md) and its capabilities. * Understanding of your specific automation and machine-to-machine communication needs. * Familiarity with [AI Security and Data Controls](/port-ai/security-and-data-controls/.md). Configure agents in Port for your domains, then connect them to workflows and external systems. See [Build agents](/agent-management/custom-agents/build-an-ai-agent/.md) and [Interact with agents](/agent-management/custom-agents/interact-with-ai-agents/.md). ## Access to the feature[​](#access-to-the-feature "Direct link to Access to the feature") Access to Port AI features (including AI Agents) is controlled through the `_ai_invocation` blueprint's permissions. Users need the **Read**, **Register**, and **Update** permissions on AI invocation entities to access and interact with AI features, including your AI Agents. After building an agent, make sure to configure the appropriate permissions so your users can actually interact with it. Permissions required for AI features to work Creating an AI agent is not enough on its own - users also need the **Read**, **Register**, and **Update** permissions on the `_ai_invocation` blueprint to be able to invoke and interact with it. [Learn how to configure access to Port AI →](/port-ai/security-and-data-controls/.md#controlling-access-to-port-ai) [Build and customize](/agent-management/custom-agents/build-an-ai-agent/.md) your AI agents for specific domains: * Define agent instructions, goals, and expertise areas. * Set allowed tools and configure autonomy levels for actions. * Grant access to external [MCP connector](/agent-management/ai-registry/mcp-connectors/.md) tools when the agent needs data or actions outside your Port catalog. * Test agent behavior and validate responses. * Integrate agents into workflow automations and external systems. ## Security and Governance[​](#security-and-governance "Direct link to Security and Governance") AI Agents inherit all security and governance controls from [Port AI](/port-ai/overview/.md). They operate within the same secure framework with additional controls for autonomous operation: * **RBAC compliance**: Agents respect all data access permissions and policies. * **Audit trail**: All agent interactions are logged as AI invocations for monitoring. * **Data governance**: Same data handling policies as [Port AI Security and Data Controls](/port-ai/security-and-data-controls/.md). For comprehensive security information, see [AI Security and Data Controls](/port-ai/security-and-data-controls/.md). ## Implementation Strategy[​](#implementation-strategy "Direct link to Implementation Strategy") Start with focused, high-value use cases that demonstrate clear ROI: 1. **Begin with monitoring and alerting** - Implement agents that enhance existing observability. 2. **Add workflow automation** - Build agents that streamline repetitive processes. 3. **Expand to complex orchestration** - Develop agents that manage multi-system workflows. 4. **Scale across domains** - Deploy specialized agents for different organizational areas. ## Data Model[​](#data-model "Direct link to Data Model") AI Agents use the same data model as [Port AI](/port-ai/overview/.md) with additional agent-specific blueprints: 1. **AI agents** (`_ai_agent`) - The agent configurations and domain specifications that define each agent's capabilities and boundaries. See [Build agents](/agent-management/custom-agents/build-an-ai-agent/.md). 2. **[AI invocations](/port-ai/ai-invocations/.md)** (`_ai_invocations`) - Each agent interaction is recorded as an invocation, providing comprehensive audit trails for autonomous operations. These records include execution context, decisions made, and outcomes for monitoring and debugging agent behavior. ## Relevant guides[​](#relevant-guides "Direct link to Relevant guides") Explore these guides to see AI agents in action and learn how to implement them in your organization: * [AI-powered incident management](/guides/all/ai-powered-incident-management/.md) * [Prepare work items with AI](/guides/all/prepare-work-items-with-ai/.md) * [Setup PR enricher AI agent](/guides/all/setup-pr-enricher-ai-agent/.md) * [Setup service explorer AI agent](/guides/all/setup-service-explorer-ai-agent/.md) * [Setup platform request triage AI agent](/guides/all/setup-platform-request-triage-ai-agent/.md) * [Setup task manager AI agent](/guides/all/setup-task-manager-ai-agent/.md) * [Setup incident manager AI agent](/guides/all/setup-incident-manager-ai-agent/.md) * [Remediate vulnerabilities with AI](/guides/all/remediate-vulnerability-with-ai/.md) ## FAQ[​](#faq "Direct link to FAQ") 1What's the difference between AI Agents and Port AI Assistant? 2Can AI Agents operate autonomously without human approval? 3How are AI Agents different from general automation tools? 4What security controls apply to autonomous AI Agents? --- # Claude managed agents ![ClaudeAI icon](/img/guides/icons/ClaudeAI.svg)![ClaudeAI icon](/img/guides/icons/ClaudeAI.svg) Loading version... Port's Claude Managed Agents integration surfaces your Anthropic agents, environments, sessions, vaults, and memory stores in your Context Lake. From there, you can trigger agent sessions directly from Port workflows or let platform engineers create new agents through self-service forms. Supported resources ![External link icon](/img/icons/external-link.svg)[agent](https://platform.claude.com/docs/en/api/beta/agents)[environment](https://platform.claude.com/docs/en/api/beta/environments)[session](https://platform.claude.com/docs/en/api/beta/sessions)[vault](https://platform.claude.com/docs/en/api/beta/vaults)[memory-store](https://platform.claude.com/docs/en/api/beta/memory_stores)[skill](https://platform.claude.com/docs/en/api/beta/skills) ## Core support [Live eventsSee supported resources](#webhook-configuration) For a full end-to-end example that combines both a create-agent and a trigger-agent workflow, see the guide [Assign PR reviewers with a Claude managed agent](/guides/all/assign-pr-reviewers-with-claude/.md). ## Trigger a Claude agent[​](#trigger-a-claude-agent "Direct link to Trigger a Claude agent") Use this workflow to start a new session or continue an existing one, and send a user message to the agent. See the [Claude managed agents integration action](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/claude-managed-agents/.md#complete-workflow-examples) page for the full workflow JSON and node field reference. For a worked example, see the guide [Assign PR reviewers with a Claude managed agent](/guides/all/assign-pr-reviewers-with-claude/.md). GitHub repository resources When a session includes a `github_repository` resource and `githubAuthorizationToken` is configured on the integration, the token is automatically injected so the session can clone and push to the repository. This works without the GitHub MCP server. To also open pull requests, the agent needs a vault with GitHub MCP credentials (see the [MCP server setup](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/claude-managed-agents/.md#github-mcp-optional-for-pull-requests) section on the integration action page). ## Create a Claude agent[​](#create-a-claude-agent "Direct link to Create a Claude agent") Use this workflow to let platform engineers or developers create a new Claude managed agent through a self-service form. See the [Claude managed agents integration action](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/claude-managed-agents/.md#complete-workflow-examples) page for the full workflow JSON and node field reference. For a worked example, see the guide [Assign PR reviewers with a Claude managed agent](/guides/all/assign-pr-reviewers-with-claude/.md). ## MCP server setup[​](#mcp-server-setup "Direct link to MCP server setup") Agents can use Port MCP or GitHub MCP during sessions. Both require a Claude credential vault with the appropriate token. For the full credential setup steps, see [MCP server setup](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/claude-managed-agents/.md#mcp-server-setup) on the integration action page. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An [Anthropic API key](https://platform.claude.com/docs/en/api/overview#prerequisites). * Your Port user role is set to `Admin`. ## Setup[​](#setup "Direct link to Setup") Choose one of the installation methods below. If you are not sure which option to use, review the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party API into Port. The mapping uses the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to transform fields and shape incoming Claude data into Port entities. ### Webhook configuration[​](#webhook-configuration "Direct link to Webhook configuration") To enable real-time updates from Claude to Port, configure a webhook in the [Claude Console](https://platform.claude.com). The webhook endpoint URL follows the format: ``` /integration/webhook ``` Where `` is: * **Hosted by Port**: the live events base URL shown in the install step above. * **Self-hosted**: the `liveEvents.baseUrl` you configured during installation. To set up the webhook: 1. In the [Claude Console](https://platform.claude.com), go to **Manage > Webhooks** and click **Add webhook endpoint**. 2. Paste the endpoint URL. 3. Under **Events to subscribe**, enable: * `session.status_idled` * `session.status_terminated` * `vault.created` * `vault.archived` * `vault.deleted` 4. Copy the **signing secret** shown at the end and set it as `webhookSigningSecret` in your integration settings. Signature verification is optional Configuring `webhookSigningSecret` is optional. When set, the integration validates the signature on every incoming webhook and rejects requests with an invalid or missing signature. When not set, incoming webhook requests are accepted without signature verification. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") **Default mapping configuration (click to expand)** ``` resources: - kind: environment selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"claude_environment"' properties: description: .description scope: .scope environmentType: .config.type metadata: .metadata createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at - kind: vault selector: query: 'true' port: entity: mappings: identifier: .id title: .display_name blueprint: '"claude_vault"' properties: metadata: .metadata createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at - kind: memory-store selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"claude_memory_store"' properties: description: .description metadata: .metadata createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at - kind: skill selector: query: 'true' port: entity: mappings: identifier: .id title: '.display_title // .id' blueprint: '"claude_skill"' properties: displayTitle: .display_title source: .source latestVersion: .latest_version createdAt: .created_at updatedAt: .updated_at - kind: agent selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"claude_agent"' properties: description: .description model: .model.id system: .system version: .version metadata: .metadata mcpServers: '[.mcp_servers[]?.name] // []' createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at relations: skills: '[.skills[]? | select(.type == "custom") | .skill_id]' delegateAgents: '[.multiagent.agents[]?.id] // []' - kind: session selector: query: 'true' port: entity: mappings: identifier: .id title: '.title // .id' blueprint: '"claude_session"' properties: status: .status inputTokens: .usage.input_tokens outputTokens: .usage.output_tokens cacheReadInputTokens: .usage.cache_read_input_tokens activeSeconds: .stats.active_seconds durationSeconds: .stats.duration_seconds agentVersion: .agent.version githubRepositories: '[.resources[]? | select(.type == "github_repository") | .url]' metadata: .metadata createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at relations: agent: .agent.id environment: .environment_id memoryStores: '[.resources[]? | select(.type == "memory_store") | .memory_store_id]' vaults: .vault_ids ``` ## Mapping & examples per resource[​](#mapping--examples-per-resource "Direct link to Mapping & examples per resource") To view and test the integration mapping against sample API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). agentenvironmentsessionvaultmemory-storeskill BlueprintJSON Create in Port ``` { "identifier": "claude_agent", "title": "Claude Agent", "icon": "Claude", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "model": { "type": "string", "title": "Model" }, "system": { "type": "string", "title": "System prompt" }, "version": { "type": "number", "title": "Version" }, "metadata": { "type": "object", "title": "Metadata" }, "mcpServers": { "type": "array", "title": "MCP servers", "items": { "type": "string" } }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" }, "archivedAt": { "type": "string", "format": "date-time", "title": "Archived At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "skills": { "title": "Skills", "target": "claude_skill", "required": false, "many": true }, "delegateAgents": { "title": "Delegate agents", "target": "claude_agent", "required": false, "many": true } } } ``` Mapping configurationYAML ``` resources: - kind: agent selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"claude_agent"' properties: description: .description model: .model.id system: .system version: .version metadata: .metadata mcpServers: '[.mcp_servers[]?.name] // []' createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at relations: skills: '[.skills[]? | select(.type == "custom") | .skill_id]' delegateAgents: '[.multiagent.agents[]?.id] // []' ``` Sample payloadJSON ``` { "id": "agt_01abc123def456", "name": "Code Review Agent", "description": "Reviews pull requests and suggests improvements.", "model": { "id": "claude-sonnet-4-5" }, "system": "You are a senior software engineer. Review code changes for correctness, style, and security.", "version": 2, "metadata": { "team": "platform", "owner": "alice" }, "mcp_servers": [ { "name": "github", "url": "https://mcp.github.com" } ], "skills": [ { "type": "custom", "skill_id": "skl_01review111" } ], "multiagent": { "type": "coordinator", "agents": [ { "id": "agt_01delegate222", "type": "agent", "version": 1 } ] }, "created_at": "2026-01-10T08:00:00Z", "updated_at": "2026-06-01T12:00:00Z", "archived_at": null } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_environment", "title": "Claude Environment", "icon": "Claude", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "scope": { "type": "string", "title": "Scope", "enum": ["organization", "account"] }, "environmentType": { "type": "string", "title": "Environment type", "enum": ["cloud", "self_hosted"] }, "metadata": { "type": "object", "title": "Metadata" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" }, "archivedAt": { "type": "string", "format": "date-time", "title": "Archived At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` resources: - kind: environment selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"claude_environment"' properties: description: .description scope: .scope environmentType: .config.type metadata: .metadata createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at ``` Sample payloadJSON ``` { "id": "env_01xyz789abc", "name": "Default Environment", "description": "Organization-wide default execution environment.", "scope": "organization", "config": { "type": "cloud" }, "metadata": { "region": "us-east-1" }, "created_at": "2026-01-05T10:00:00Z", "updated_at": "2026-05-20T09:30:00Z", "archived_at": null } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_session", "title": "Claude Session", "icon": "Claude", "schema": { "properties": { "status": { "type": "string", "title": "Status", "enum": ["rescheduling", "running", "idle", "terminated"], "enumColors": { "rescheduling": "yellow", "running": "blue", "idle": "green", "terminated": "red" } }, "inputTokens": { "type": "number", "title": "Input tokens" }, "outputTokens": { "type": "number", "title": "Output tokens" }, "cacheReadInputTokens": { "type": "number", "title": "Cache read input tokens" }, "activeSeconds": { "type": "number", "title": "Active seconds" }, "durationSeconds": { "type": "number", "title": "Duration seconds" }, "agentVersion": { "type": "number", "title": "Agent version" }, "githubRepositories": { "type": "array", "title": "GitHub repositories", "items": { "type": "string" } }, "metadata": { "type": "object", "title": "Metadata" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" }, "archivedAt": { "type": "string", "format": "date-time", "title": "Archived At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "agent": { "title": "Agent", "target": "claude_agent", "required": false, "many": false }, "environment": { "title": "Environment", "target": "claude_environment", "required": false, "many": false }, "memoryStores": { "title": "Memory stores", "target": "claude_memory_store", "required": false, "many": true }, "vaults": { "title": "Vaults", "target": "claude_vault", "required": false, "many": true } } } ``` Mapping configurationYAML ``` resources: - kind: session selector: query: 'true' port: entity: mappings: identifier: .id title: '.title // .id' blueprint: '"claude_session"' properties: status: .status inputTokens: .usage.input_tokens outputTokens: .usage.output_tokens cacheReadInputTokens: .usage.cache_read_input_tokens activeSeconds: .stats.active_seconds durationSeconds: .stats.duration_seconds agentVersion: .agent.version githubRepositories: '[.resources[]? | select(.type == "github_repository") | .url]' metadata: .metadata createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at relations: agent: .agent.id environment: .environment_id memoryStores: '[.resources[]? | select(.type == "memory_store") | .memory_store_id]' vaults: .vault_ids ``` Sample payloadJSON ``` { "id": "ses_01pqr456stu", "title": "Review PR #142", "status": "idle", "agent": { "id": "agt_01abc123def456", "version": 2 }, "environment_id": "env_01xyz789abc", "vault_ids": ["vlt_01aaa111bbb"], "resources": [ { "type": "github_repository", "url": "https://github.com/my-org/my-repo" }, { "type": "memory_store", "memory_store_id": "mem_01ccc222ddd" } ], "usage": { "input_tokens": 4200, "output_tokens": 1800, "cache_read_input_tokens": 500 }, "stats": { "active_seconds": 47.3, "duration_seconds": 120.0 }, "metadata": { "pr_number": "142", "repo": "my-org/my-repo" }, "created_at": "2026-06-15T14:00:00Z", "updated_at": "2026-06-15T14:05:30Z", "archived_at": null } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_vault", "title": "Claude Vault", "icon": "Claude", "schema": { "properties": { "metadata": { "type": "object", "title": "Metadata" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" }, "archivedAt": { "type": "string", "format": "date-time", "title": "Archived At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` resources: - kind: vault selector: query: 'true' port: entity: mappings: identifier: .id title: .display_name blueprint: '"claude_vault"' properties: metadata: .metadata createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at ``` Sample payloadJSON ``` { "id": "vlt_01aaa111bbb", "display_name": "Port MCP", "metadata": { "purpose": "port-mcp-credentials" }, "created_at": "2026-02-01T09:00:00Z", "updated_at": "2026-02-01T09:00:00Z", "archived_at": null } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_memory_store", "title": "Claude Memory Store", "icon": "Claude", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "metadata": { "type": "object", "title": "Metadata" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" }, "archivedAt": { "type": "string", "format": "date-time", "title": "Archived At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` resources: - kind: memory-store selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"claude_memory_store"' properties: description: .description metadata: .metadata createdAt: .created_at updatedAt: .updated_at archivedAt: .archived_at ``` Sample payloadJSON ``` { "id": "mem_01ccc222ddd", "name": "Engineering Context", "description": "Long-term memory for engineering best practices and team conventions.", "metadata": { "team": "platform" }, "created_at": "2026-03-10T11:00:00Z", "updated_at": "2026-06-20T08:45:00Z", "archived_at": null } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_skill", "title": "Claude Skill", "icon": "Claude", "schema": { "properties": { "displayTitle": { "type": "string", "title": "Display title" }, "source": { "type": "string", "title": "Source", "enum": ["custom", "anthropic"] }, "latestVersion": { "type": "string", "title": "Latest version" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` resources: - kind: skill selector: query: 'true' port: entity: mappings: identifier: .id title: '.display_title // .id' blueprint: '"claude_skill"' properties: displayTitle: .display_title source: .source latestVersion: .latest_version createdAt: .created_at updatedAt: .updated_at ``` Sample payloadJSON ``` { "id": "skl_01eee333fff", "display_title": "Port Catalog Reader", "source": "custom", "latest_version": "1.2.0", "created_at": "2026-04-01T10:00:00Z", "updated_at": "2026-06-10T14:00:00Z" } ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # Cursor cloud agents ![Cursor icon](/img/guides/icons/Cursor.svg)![Cursor icon](/img/guides/icons/dark/Cursor.svg) Loading version... Port's Cursor Cloud Agents integration surfaces your Cursor cloud agents and their runs in your Context Lake. From there, you can launch new agents or send follow-up prompts to existing ones directly from Port workflows. Supported resources ![External link icon](/img/icons/external-link.svg)[agent](https://cursor.com/docs/cloud-agent/api/endpoints#list-agents)[run](https://cursor.com/docs/cloud-agent/api/endpoints#list-runs) ## Core support [Live eventsSee supported resources](#webhook-configuration) ## Trigger a Cursor cloud agent[​](#trigger-a-cursor-cloud-agent "Direct link to Trigger a Cursor cloud agent") Use this workflow to send a follow-up prompt to an existing Cursor cloud agent. See the [Cursor Cloud Agents integration action](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/cursor-cloud-agents/.md#trigger-agent) page for the full node field reference. ## Create a Cursor cloud agent[​](#create-a-cursor-cloud-agent "Direct link to Create a Cursor cloud agent") Use this workflow to let platform engineers or developers launch a new Cursor cloud agent through a self-service form. See the [Cursor Cloud Agents integration action](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/cursor-cloud-agents/.md#create-agent) page for the full node field reference. Create also launches a run Launching a new Cursor cloud agent always starts a run and billable work immediately. See [Action semantics](/workflows/build-workflows/nodes/action-nodes/integration-actions/ai/cursor-cloud-agents/.md#action-semantics) on the integration action page for details. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A [Cursor API key](https://cursor.com/dashboard?tab=integrations) with access to the Cloud Agents API. * Optionally configure a **webhook signing secret** (`webhookSigningSecret`) to verify incoming Cursor webhooks. The value must be at least 32 characters. Generate a random string with `openssl rand -hex 32` and provide it during installation. When unset, webhook signature verification is skipped. * Your Port user role is set to `Admin`. Use a service account key On the Cursor Enterprise plan, you can use a [service account](https://cursor.com/docs/account/enterprise/service-accounts) API key as the integration's `cursor_api_key` instead of a personal key. Runs are then attributed to the service account rather than an individual, so the integration keeps working as people and roles change. This requires the Cursor GitHub app to be connected at the team level with the target repositories authorized, and usage bills to your team's usage pool. Note that a service account key cannot reuse a user's saved MCP OAuth logins. ## Setup[​](#setup "Direct link to Setup") Choose one of the installation methods below. If you are not sure which option to use, review the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party API into Port. The mapping uses the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to transform fields and shape incoming Cursor data into Port entities. ### Resource selectors[​](#resource-selectors "Direct link to Resource selectors") In addition to `query`, each resource supports optional **selector** fields in your Port App config: | Resource | Field | Default | Description | | -------- | --------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | `agent` | `includeArchived` | `false` | Include archived agents from Cursor's List Agents API. | | `run` | `includeArchived` | `false` | Include archived agents when fanning out to List Runs (Cursor has no separate archived filter on List Runs). | | `run` | `enrichRunsWithUsage` | `true` | Fetch token usage per agent and merge it into each run during sync. Set to `false` to skip usage API calls. | | `run` | `oldestRunDate` | unset | ISO 8601 UTC timestamp. Stop paginating runs for each agent once a run older than this cutoff is reached (runs are newest-first). | Example with a run sync window and usage enrichment disabled: ``` resources: - kind: agent selector: query: 'true' includeArchived: false port: entity: mappings: identifier: .id title: .name blueprint: '"cursor_agent"' - kind: run selector: query: 'true' includeArchived: false enrichRunsWithUsage: false oldestRunDate: "2026-01-01T00:00:00Z" port: entity: mappings: identifier: .id title: .id blueprint: '"cursor_run"' ``` ### Webhook configuration[​](#webhook-configuration "Direct link to Webhook configuration") Cursor's v0 API attaches a webhook per agent at launch time - there is no account-wide webhook subscription to configure in a Cursor console. On **create\_agent**, set `apiVersion` to `v0` and `reportCompletion` to `true`; the integration then attaches a static callback URL as the agent's webhook when it launches it. **trigger\_agent** follow-ups always use the v1 API but still receive completion callbacks on agents that were v0-created with a webhook. ``` /integration/webhook ``` Where `` is: * **Hosted by Port**: the live events base URL shown in the install step above. * **Self-hosted**: the `liveEvents.baseUrl` you configured during installation. Optionally configure **`webhookSigningSecret`** on the integration (a sensitive installation setting). When set, it must be at least 32 characters. That value is sent to Cursor as the webhook secret and used to validate the `X-Webhook-Signature` header on incoming webhooks. When unset, signature verification is skipped. Recommended for production Configure `webhookSigningSecret` so incoming webhooks are authenticated. Without it, any caller that knows your callback URL can post status updates. Use at least 32 characters. `openssl rand -hex 32` produces a 64-character value that meets Cursor's requirement. Generate a value with for example: ``` openssl rand -hex 32 ``` Store it as an organization secret during SaaS installation, or set `OCEAN__INTEGRATION__CONFIG__WEBHOOK_SIGNING_SECRET` for self-hosted deployments. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") **Default mapping configuration (click to expand)** ``` resources: - kind: agent selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"cursor_agent"' properties: status: .status url: .url repoUrls: | [.repos[]?.url // empty] | map(select(. != null and . != "")) | map(if startswith("http") then . else "https://" + . end) | map(select(test("^https?://.+"))) createdAt: .createdAt updatedAt: .updatedAt - kind: run selector: query: 'true' port: entity: mappings: identifier: .id title: .id blueprint: '"cursor_run"' properties: status: .status durationMs: .durationMs branches: '[.git.branches[]?.branch | select(. != null and . != "")] // []' prUrls: '[.git.branches[]?.prUrl? | select(. != null)] // []' inputTokens: .usage.inputTokens outputTokens: .usage.outputTokens cacheReadTokens: .usage.cacheReadTokens cacheWriteTokens: .usage.cacheWriteTokens totalTokens: .usage.totalTokens createdAt: .createdAt updatedAt: .updatedAt relations: agent: .agentId ``` ## Mapping & examples per resource[​](#mapping--examples-per-resource "Direct link to Mapping & examples per resource") To view and test the integration mapping against sample API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Run token usage Cursor's List Runs and Get Run endpoints don't return token usage inline. When `enrichRunsWithUsage` is `true` (the default), the integration makes one extra request per agent to Cursor's usage endpoint and merges each run's usage into its payload before mapping, so the `usage` field shown in the sample payload below isn't present in Cursor's own List Runs response. Set `enrichRunsWithUsage` to `false` on the `run` resource to skip those calls. agentrun BlueprintJSON Create in Port ``` { "identifier": "cursor_agent", "title": "Cursor Agent", "icon": "Cursor", "schema": { "properties": { "status": { "type": "string", "title": "Status", "enum": ["ACTIVE", "IDLE", "ARCHIVED"], "enumColors": { "ACTIVE": "blue", "IDLE": "yellow", "ARCHIVED": "lightGray" } }, "url": { "type": "string", "title": "Cursor URL", "format": "url" }, "repoUrls": { "type": "array", "title": "Repository URLs", "items": { "type": "string", "format": "url" } }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` resources: - kind: agent selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"cursor_agent"' properties: status: .status url: .url repoUrls: | [.repos[]?.url // empty] | map(select(. != null and . != "")) | map(if startswith("http") then . else "https://" + . end) | map(select(test("^https?://.+"))) createdAt: .createdAt updatedAt: .updatedAt ``` Sample payloadJSON ``` { "id": "bc-35b4cb3f-5817-4859-bd44-115a68019c27", "name": "Mcp server user access", "status": "ARCHIVED", "env": { "type": "cloud" }, "repos": [ { "url": "github.com/talsabagport/kubectl-opslevel", "startingRef": "main" } ], "workOnCurrentBranch": false, "autoCreatePR": false, "skipReviewerRequest": false, "url": "https://cursor.com/agents/bc-35b4cb3f-5817-4859-bd44-115a68019c27", "createdAt": "2026-06-04T10:59:39.801Z", "updatedAt": "2026-06-09T14:29:11.846Z", "latestRunId": "run-a097bb31-bde3-45d2-8492-34e5e5ebd01a" } ``` BlueprintJSON Create in Port ``` { "identifier": "cursor_run", "title": "Cursor Run", "icon": "Cursor", "schema": { "properties": { "status": { "type": "string", "title": "Status", "enum": ["CREATING", "RUNNING", "FINISHED", "ERROR", "CANCELLED", "EXPIRED"], "enumColors": { "CREATING": "lightGray", "RUNNING": "blue", "FINISHED": "green", "ERROR": "red", "CANCELLED": "yellow", "EXPIRED": "yellow" } }, "durationMs": { "type": "number", "title": "Duration (ms)" }, "branches": { "type": "array", "title": "Pushed branches", "items": { "type": "string" } }, "prUrls": { "type": "array", "title": "Pull request URLs", "items": { "type": "string", "format": "url" } }, "inputTokens": { "type": "number", "title": "Input tokens" }, "outputTokens": { "type": "number", "title": "Output tokens" }, "cacheReadTokens": { "type": "number", "title": "Cache read tokens" }, "cacheWriteTokens": { "type": "number", "title": "Cache write tokens" }, "totalTokens": { "type": "number", "title": "Total tokens" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "agent": { "title": "Agent", "target": "cursor_agent", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: run selector: query: 'true' port: entity: mappings: identifier: .id title: .id blueprint: '"cursor_run"' properties: status: .status durationMs: .durationMs branches: '[.git.branches[]?.branch | select(. != null and . != "")] // []' prUrls: '[.git.branches[]?.prUrl? | select(. != null)] // []' inputTokens: .usage.inputTokens outputTokens: .usage.outputTokens cacheReadTokens: .usage.cacheReadTokens cacheWriteTokens: .usage.cacheWriteTokens totalTokens: .usage.totalTokens createdAt: .createdAt updatedAt: .updatedAt relations: agent: .agentId ``` Sample payloadJSON ``` { "id": "run-abc12345-6789-4def-a012-3456789abcde", "agentId": "bc-abc12345-6789-4def-a012-3456789abcde", "status": "FINISHED", "durationMs": 184320, "git": { "branches": [ { "branch": "cursor/fix-flaky-checkout-test", "prUrl": "https://github.com/your-org/your-repo/pull/482" } ] }, "usage": { "inputTokens": 6160, "outputTokens": 1660, "cacheWriteTokens": 11100, "cacheReadTokens": 21300, "totalTokens": 40220 }, "createdAt": "2026-07-10T08:00:00Z", "updatedAt": "2026-07-10T08:03:04Z" } ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # External agents External agents are AI agents you build and run outside Port. They fall into two common patterns: * **Code-first agents** — built with SDKs in your own code, repos, or CI (for example OpenAI Agents SDK, Anthropic Claude Agent SDK, Claude Code, or agents running in GitHub Actions). * **Cloud-managed agents** — hosted and operated on a vendor control plane (for example AWS Bedrock / AgentCore, Azure AI Foundry, or Cursor Cloud Agents). Port connects to these agents so you can see which ones exist in your organization, what they can access, and trigger them from your workflows. ## When teams lack a single view of external agents[​](#when-teams-lack-a-single-view-of-external-agents "Direct link to When teams lack a single view of external agents") As adoption grows, agents on external platforms often spread faster than your governance model: * Teams deploy code-first agents in repos and cloud-managed agents on Bedrock, Foundry, Claude, and other platforms independently. * Platform and security leads cannot answer basic questions: which agents are running, what data they can reach, or whether two teams built the same agent twice. * Without inventory, you cannot assign ownership, enforce approval flows, or audit what an agent did after the fact. ## What Port gives you[​](#what-port-gives-you "Direct link to What Port gives you") | Capability | What it means for you | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Context Lake discovery** | Agents, environments, sessions, skills, and vaults from connected platforms appear as entities in your [Context Lake](/context-lake/overview/.md). | | **Workflow operations** | Start sessions, send messages, or create agents through [Port workflows](/workflows/overview/.md) instead of switching to each vendor console. | | **Data access control** | Agents respect [Context Lake permissions](/context-lake/consuming-the-lake/govern-data-access/overview/.md) — access to data is scoped to what their role allows. | | **Cross-platform registry** | Use an [agent registry](/agent-management/ai-registry/agent-registry/.md) to track code-first and cloud-managed deployments in one view. | ## Get started by platform[​](#get-started-by-platform "Direct link to Get started by platform") | Platform | Get started | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Claude managed agents** | Surface your Anthropic agents, environments, sessions, vaults, and memory stores in your Context Lake, then trigger sessions or create agents from Port workflows. [Claude managed agents](/agent-management/external-agents/claude-managed-agents/.md). | | **Cursor cloud agents** | Surface your Cursor cloud agents and their runs in your Context Lake, then launch new agents or send follow-up prompts from Port workflows. [Cursor cloud agents](/agent-management/external-agents/cursor-cloud-agents/.md). | | **Cloud managed agents** | Maintain a governed registry for agents on AWS Bedrock, Azure AI Foundry, and other cloud platforms without a native Port connector. [Build an AI Agent Registry](/guides/all/build-ai-agent-registry/.md). | ## Related capabilities[​](#related-capabilities "Direct link to Related capabilities") * [AI registry](/agent-management/ai-registry/mcp-registry/.md) — govern skills, prompts, and MCP servers that agents use when they connect to Port. * [Port MCP server](/agent-management/port-mcp-server/overview/.md) — connect IDEs and external tools to your Context Lake. --- # Agent management Agent management is about governing every AI agent operating in your organization - what data they can access, what actions they can take, and who can use them. That includes **code-first agents** you build with SDKs in your own repos or CI (for example OpenAI Agents SDK, Anthropic Claude Agent SDK, or Claude Code), **cloud-managed agents** hosted on vendor control planes (for example AWS Bedrock / AgentCore, Azure AI Foundry, or Cursor Cloud Agents), and **Port-native agents** configured inside Port. ## What roles do AI agents play in Port?[​](#what-roles-do-ai-agents-play-in-port "Direct link to What roles do AI agents play in Port?") Agents interact with your software factory in three complementary ways. Understanding these patterns helps you decide when to let an existing agent consume Port, when the platform should trigger agents on its own, and when to provision a new agent. ### AI agents as platform consumers[​](#ai-agents-as-platform-consumers "Direct link to AI agents as platform consumers") The most intuitive role is the agent as a user of the platform. For example, an engineer asks a coding agent to add an endpoint to a payments service. Before writing code, the agent pulls the service's owner, dependencies, and standards from Port, then spins up a preview environment and runs tests through self-service workflows. ![Claude Code agent reads context from Port and runs platform actions such as creating a preview environment and running tests](/img/agent-management/ai-agents-as-platform-consumers.png)

Here the agent works side by side with the developer. It reads from Context Lake and runs governed actions, the same way a human would use the platform. Use this pattern when developers already work in tools like Claude Code, Cursor, or GitHub Copilot. The [Port MCP server](/agent-management/port-mcp-server/overview/.md) exposes catalog context and workflows so those agents operate with the same source of truth and guardrails as humans. ### AI agents as internal platform components[​](#ai-agents-as-internal-platform-components "Direct link to AI agents as internal platform components") The next level treats the agent as part of the platform engine, not only as a user of it. For example, when a vulnerability is opened, Port can take a remediation agent from the [agent registry](/agent-management/ai-registry/agent-registry/.md) (or invoke an agent for that case without using a registered one) and run it inside a workflow to remediate across affected services. ![Agent registry remediation agent triggered by a nightly scan, opening pull requests across many services](/img/agent-management/ai-agents-as-internal-platform-components.png)

Nobody has to request an agent for each case. The platform triggers the agent from an event, and owning teams wake up to outcomes such as open PRs. Use this pattern for autonomous work such as security remediation, self-healing incidents, or other event-driven loops where the agent is a pillar of the platform itself. ### AI as reusable building blocks[​](#ai-as-reusable-building-blocks "Direct link to AI as reusable building blocks") The third role is AI as a reusable building block. For example, an engineer needs an agent. They pick the model, tools, network landscape, and use case, and the platform hands back an agent that already sits on the golden path, with identity, approved tool access, and the right scope. ![Engineer requests an agent via a self-service workflow and receives a registered, governed agent with identity and tool access](/img/agent-management/ai-as-reusable-building-blocks.png)

"Agent" here also covers related AI artifacts such as LLMs, MCP servers, and skills. Teams often struggle to enforce the right tools and access, apply guardrails the way they would for any other service or resource, and hand over an agent once it is built. Self-service workflows such as **Create an agent**, **Onboard an LLM**, **Add MCP server**, and **Publish a skill** turn that lifecycle into a governed process for creating, registering, and sharing AI artifacts across the organization. ## Govern agents, skills, and MCP at scale[​](#govern-agents-skills-and-mcp-at-scale "Direct link to Govern agents, skills, and MCP at scale") As engineering teams adopt AI agents, the number of agents, skills, prompts, and MCP servers grows quickly. Without a shared layer to discover and govern them, each team operates in isolation, and platform teams lose the ability to enforce standards or answer basic operational questions. Without shared governance, agents, skills, prompts, and MCP servers end up scattered across teams, making it hard to see what exists, control access, or avoid duplication. Each team maintains separate configurations and there is no single place to track ownership, security, or approved tools. ### How Port helps[​](#how-port-helps "Direct link to How Port helps") Port gives you one place to discover and govern agents, skills, prompts, and MCP across your organization: | You need to… | Port capability | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Route and govern model traffic at scale, then connect gateway data to teams and services | [AI Gateway](/agent-management/ai-gateway/.md) | | Discover agents on external platforms, code-first (OpenAI Agents SDK, Claude Code, GitHub Actions) or cloud-managed (Bedrock / AgentCore, Azure AI Foundry, Cursor Cloud Agents) | [External agents](/agent-management/external-agents/overview/.md) | | Maintain a registry of external agents with ownership and status in Context Lake | [Agent registry](/agent-management/ai-registry/agent-registry/.md) | | Govern which skills, prompts, and MCP servers teams may use | [AI registry](/agent-management/ai-registry/skills/overview/.md) (skills, [prompts](/agent-management/ai-registry/prompts/.md), [MCP registry](/agent-management/ai-registry/mcp-registry/.md)) | | Build and run agents natively in Port | [Port custom agents](/agent-management/custom-agents/overview/.md) | | Scope what agents can read from Context Lake | [Context Lake data access](/context-lake/consuming-the-lake/govern-data-access/overview/.md) | | Connect IDEs and external tools to your Context Lake | [Port MCP server](/agent-management/port-mcp-server/overview/.md) | Port brings agents, skills, prompts, and MCP under one governance layer so teams can move fast without losing control. ## What's in this section[​](#whats-in-this-section "Direct link to What's in this section") ### AI Gateway[​](#ai-gateway "Direct link to AI Gateway") AI gateways route every model call through a single control point for routing, budgets, guardrails, and observability. Port sits on top of that layer, connecting gateway data to your software catalog, self-service workflows, and scorecards so you can govern agents, keys, and tools at organizational scale. **[Learn about AI gateways →](/agent-management/ai-gateway/.md)** ### External agents[​](#external-agents "Direct link to External agents") Connect and govern agents hosted outside Port, whether you build them with SDKs in code or run them on cloud-managed platforms. Discover agents in Context Lake, trigger sessions from workflows, and track deployments through an agent registry. **[Explore external agents →](/agent-management/external-agents/overview/.md)** ### Port MCP server[​](#port-mcp-server "Direct link to Port MCP server") Connect IDEs, AI tools, and agents to Port using natural language. The MCP server exposes your Context Lake and workflows as callable tools, so agents like Claude, Cursor, and GitHub Copilot can query the catalog and take governed actions from wherever developers already work. **[Learn more about the Port MCP server →](/agent-management/port-mcp-server/overview/.md)** ### Port custom agents[​](#port-custom-agents "Direct link to Port custom agents") Customize and orchestrate complicated workflows inside Port. Build intelligent agents that can be used as part of automations and engineering workflows. **Use Port custom agents to:** * Automate incident response workflows * Create intelligent PR review processes * Build custom task management assistants * Generate automated deployment reports * Orchestrate multi-step engineering processes **[Explore Port custom agents →](/agent-management/custom-agents/overview/.md)** ### AI registry[​](#ai-registry "Direct link to AI registry") Connect IDEs and agents to Port, publish skills and prompts, and govern which MCP servers teams may use. This part of Agent management groups the [Port MCP server](/agent-management/port-mcp-server/overview/.md), [MCP registry](/agent-management/ai-registry/mcp-registry/.md), [Skills](/agent-management/ai-registry/skills/overview/.md), [Skills usage analytics](/agent-management/ai-registry/skills/skills-usage-analytics/.md), and [Prompts](/agent-management/ai-registry/prompts/.md) documentation. **Use the AI registry to:** * Install and secure the Port MCP server for builders and developers. * Track approved MCP servers and installation guidance in Context Lake. * Define skills and prompt blueprints that Port AI and MCP clients can load at runtime. * Measure skill adoption from Cursor and Claude integrations. **[Set up the Port MCP server →](/agent-management/port-mcp-server/installation/.md)** ## FAQ[​](#faq "Direct link to FAQ") 1What does agent management cover in Port? 2What roles can AI agents play in Port? 3How do external AI agents connect to Port? 4Can an AI agent take actions without human approval? 5What is the AI registry? --- # Available tools The Port MCP Server exposes different sets of tools based on your role and use case. The tools you see will depend on your permissions in Port. * Developer * Builder **Developers** are typically users who consume and interact with the developer portal - querying services, running actions, and analyzing data. These tools help you get information and execute approved workflows. Actions & workflows9 tools `get_action_permissions` Get permissions/approval config for actions. [API Reference](/api-reference/get-an-actions-permissions) `get_workflow_run` Get the status and details of a workflow run by its identifier. Use after trigger\_run to poll for completion, or to inspect any previously-triggered run. Returns status, result, per-node execution details, and timestamps. [API Reference](/api-reference/get-a-workflow-run-by-identifier) `list_actions` List actions and automations in your organization. Without identifiers, returns a summary list. With identifiers, returns full action details including complete input schemas. Self-service workflow triggers are a separate concept - use `list_self_service_triggers` to discover those. [API Reference](/api-reference/get-actions-automations) `list_self_service_triggers` List self-service workflow triggers the caller can execute. Returns workflow metadata, the trigger nodeIdentifier, and the input schema for each trigger. Use before `trigger_run` to discover available operations and required inputs. [API Reference](/api-reference/list-self-service-workflow-triggers) `list_workflows` List workflows in your organization. Without identifiers, returns a summary list. With identifiers, returns full workflow details including trigger configuration and node definitions. [API Reference](/api-reference/get-workflows) `run_action`Dynamic Execute a self-service action or automation by identifier. Prefer `trigger_run` when you want one tool that covers both workflow runs and action runs. [API Reference](/api-reference/execute-a-self-service-action) `submit_input_node_response` Submit a response to a workflow input node that is waiting for input, on behalf of the current user. Get the workflow run and node run identifiers from the user, call `get_run` and read the input node run's `output.evaluatedConfig` for the description, form, and buttons, then submit the chosen button and inputs. Only responders on the node can submit. [API Reference](/api-reference/submit-a-response-to-an-input-node-run) `track_action_run` Track action execution status. [API Reference](/api-reference/get-an-action-runs-details) `trigger_run` Trigger a self-service workflow or action run. For workflows, pass `type: "WORKFLOW"`, the workflow identifier, the `nodeIdentifier` from `list_self_service_triggers`, and inputs matching the trigger schema. For actions, pass `type: "ACTION"` and the action identifier. Returns the run identifier and status so you can track progress. [API Reference](/api-reference/trigger-a-workflow-run) Audit logs1 tool `search_audit_logs` Search entity audit logs using structured filters for status, operation, trigger origin, date, errors, and related resources. Supports limit and offset pagination, optional response fields, and filtering entities or blueprints by identifier. [API Reference](/api-reference/search-entity-audit-logs) Data model3 tools `list_blueprints` List blueprints in your organization. Without identifiers, returns a summary list. With identifiers, returns full blueprint details including property definitions, schemas, and enum values. [API Reference](/api-reference/get-all-blueprints) `list_entities` Query entities from a blueprint with filtering, sorting, pagination, and `include`. Supports identifiers, `groupBy` for value distribution, and `countOnly` for a total count. [API Reference](/api-reference/search-a-blueprints-entities) ▶Behavior tips ( 7 ) `simulate_blueprint_permissions` Simulate blueprint entity permissions for a user. Use list mode to return entities the user can access for a blueprint; use detail mode with entityIdentifier to check permissions for a specific entity. [API Reference](/api-reference/simulate-permissions-for-a-user) General3 tools `describe_user_details` Describe the user, which organization he is connected to and what teams he is a member of and more information regarding the user. Can be useful for questions that relate to the user or when looking up for related entities to the user [API Reference](/api-reference/get-organization-details) `load_skill` Load specialized guidance for a domain-specific task. Skills provide step-by-step instructions for common workflows like troubleshooting integrations. Load a skill before starting a specialized task to ensure consistent, thorough handling. [Documentation](/agent-management/ai-registry/skills/overview/.md) `search_port_knowledge_sources` Search the official Port documentation and return the most relevant sections from it for a user query. Each returned section includes the url and its actual content in markdown. Use this tool for all queries that require Port knowledge. Pages & dashboards3 tools `get_page`Beta Get a specific page by identifier. Returns full page details including widgets configuration. For entity pages (type "entity"), returns a tabs array with each tab's title, widgets, layout, and reserved flag. [API Reference](/api-reference/get-a-page) `get_sidebar`Beta Get the software catalog sidebar structure including all pages and folders. [API Reference](/api-reference/pages) `load_widget_schema`Beta Get the JSON schema for a specific dashboard widget type. Call this before upsert\_dashboard\_page, upsert\_widget, or update\_entity\_page to understand the required structure for the widget you want to create. Scorecards1 tool `list_scorecards` List scorecards in your organization. Without identifiers, returns a summary list. With identifiers, returns full scorecard details including complete rule configurations. [API Reference](/api-reference/get-all-scorecards) **Builders** are platform engineers or admins who design and configure the developer portal - creating blueprints, setting up scorecards, and managing the overall structure. These tools help you build and maintain the portal. Builders have access to all the tools available to Developers, plus additional management tools for creating and configuring the portal. Actions & workflows14 tools `delete_action` Delete action by identifier. [API Reference](/api-reference/delete-an-action-automation) `delete_workflow` Delete workflow by identifier. [API Reference](/api-reference/delete-a-workflow) `get_action_permissions` Get permissions/approval config for actions. [API Reference](/api-reference/get-an-actions-permissions) `get_workflow_run` Get the status and details of a workflow run by its identifier. Use after trigger\_run to poll for completion, or to inspect any previously-triggered run. Returns status, result, per-node execution details, and timestamps. [API Reference](/api-reference/get-a-workflow-run-by-identifier) `list_actions` List actions and automations in your organization. Without identifiers, returns a summary list. With identifiers, returns full action details including complete input schemas. Self-service workflow triggers are a separate concept - use `list_self_service_triggers` to discover those. [API Reference](/api-reference/get-actions-automations) `list_self_service_triggers` List self-service workflow triggers the caller can execute. Returns workflow metadata, the trigger nodeIdentifier, and the input schema for each trigger. Use before `trigger_run` to discover available operations and required inputs. [API Reference](/api-reference/list-self-service-workflow-triggers) `list_workflows` List workflows in your organization. Without identifiers, returns a summary list. With identifiers, returns full workflow details including trigger configuration and node definitions. [API Reference](/api-reference/get-workflows) `run_action`Dynamic Execute a self-service action or automation by identifier. Prefer `trigger_run` when you want one tool that covers both workflow runs and action runs. [API Reference](/api-reference/execute-a-self-service-action) `submit_input_node_response` Submit a response to a workflow input node that is waiting for input, on behalf of the current user. Get the workflow run and node run identifiers from the user, call `get_run` and read the input node run's `output.evaluatedConfig` for the description, form, and buttons, then submit the chosen button and inputs. Only responders on the node can submit. [API Reference](/api-reference/submit-a-response-to-an-input-node-run) `track_action_run` Track action execution status. [API Reference](/api-reference/get-an-action-runs-details) `trigger_run` Trigger a self-service workflow or action run. For workflows, pass `type: "WORKFLOW"`, the workflow identifier, the `nodeIdentifier` from `list_self_service_triggers`, and inputs matching the trigger schema. For actions, pass `type: "ACTION"` and the action identifier. Returns the run identifier and status so you can track progress. [API Reference](/api-reference/trigger-a-workflow-run) `update_action_permissions` Update action permissions configuration. [API Reference](/api-reference/update-an-actions-permissions) `upsert_action` Create or update an action. Updates if the action exists, creates if it does not. API Reference links to create; updates use `change-an-action-automation`. [API Reference](/api-reference/create-an-action-automation) `upsert_workflow` Create or update a workflow. Updates if the workflow exists, creates if it does not. [API Reference](/api-reference/create-a-workflow) Audit logs1 tool `search_audit_logs` Search entity audit logs using structured filters for status, operation, trigger origin, date, errors, and related resources. Supports limit and offset pagination, optional response fields, and filtering entities or blueprints by identifier. [API Reference](/api-reference/search-entity-audit-logs) Data model8 tools `delete_blueprint` Delete blueprint and all its entities. [API Reference](/api-reference/delete-a-blueprint) `delete_entity` Delete entity with optional dependents. [API Reference](/api-reference/delete-an-entity) `list_blueprints` List blueprints in your organization. Without identifiers, returns a summary list. With identifiers, returns full blueprint details including property definitions, schemas, and enum values. [API Reference](/api-reference/get-all-blueprints) `list_entities` Query entities from a blueprint with filtering, sorting, pagination, and `include`. Supports identifiers, `groupBy` for value distribution, and `countOnly` for a total count. [API Reference](/api-reference/search-a-blueprints-entities) ▶Behavior tips ( 7 ) `simulate_blueprint_permissions` Simulate blueprint entity permissions for a user. Use list mode to return entities the user can access for a blueprint; use detail mode with entityIdentifier to check permissions for a specific entity. [API Reference](/api-reference/simulate-permissions-for-a-user) `trigger_auto_discovery` Start auto-discovery to suggest or find entities for a blueprint. Call list\_blueprints first to obtain the exact blueprint identifier before invoking this tool. Supports optional user context and excluded blueprints as supported by the tool. [API Reference](/api-reference/create-an-auto-discovery-invocation) `upsert_blueprint` Create or update a blueprint. Updates if the blueprint exists, creates if it does not. [API Reference](/api-reference/create-a-blueprint) `upsert_entity` Create or update an entity. Updates if the entity exists, creates if it does not. Uses merge by default to preserve existing fields. [API Reference](/api-reference/create-an-entity) External interfaces3 tools `list_external_interface_apps` List external interface apps registered for your organization (public clients using PKCE). Requires organization admin. [Documentation](/platform-administration/sso-authentication/manage-your-oauth-apps/.md) `revoke_external_interface_app` Revoke (permanently delete) an external interface app registration. Any UI built against it will immediately lose the ability to authenticate. Requires organization admin permissions. [Documentation](/platform-administration/sso-authentication/manage-your-oauth-apps/.md) `upsert_external_interface_app` Create or update an external interface app (public/PKCE OAuth client, no client secret) for your organization, so you can build a custom UI against Port's API. Omit appId to create; pass appId to update. Requires organization admin permissions. [Documentation](/platform-administration/sso-authentication/manage-your-oauth-apps/.md) ▶Behavior tips ( 2 ) General3 tools `describe_user_details` Describe the user, which organization he is connected to and what teams he is a member of and more information regarding the user. Can be useful for questions that relate to the user or when looking up for related entities to the user [API Reference](/api-reference/get-organization-details) `load_skill` Load specialized guidance for a domain-specific task. Skills provide step-by-step instructions for common workflows like troubleshooting integrations. Load a skill before starting a specialized task to ensure consistent, thorough handling. [Documentation](/agent-management/ai-registry/skills/overview/.md) `search_port_knowledge_sources` Search the official Port documentation and return the most relevant sections from it for a user query. Each returned section includes the url and its actual content in markdown. Use this tool for all queries that require Port knowledge. Integrations5 tools `get_integration_event_logs` Fetch event logs for an integration. Defaults to ERROR-level logs. Supports filters for levels, event\_id, etl\_phases, and kinds. Paginate with timestamp and log\_id from the previous response. [API Reference](/api-reference/get-an-integrations-event-logs) `get_integration_kinds_with_examples` Fetch all kinds (data types) that an integration ingests, along with raw data examples for each kind. For example, a GitHub integration might have kinds like "pull\_request", "issue", and "repo". `get_integration_sync_metrics` Fetch sync metrics for an integration. Returns detailed metrics including status, number of items handled in each phase of the integration pipeline (extract, transform, load, delete), and per-kind statistics. [API Reference](/api-reference/get-an-integrations-metrics-and-sync-status) `list_integrations` List integrations in your organization. Without identifiers, returns a summary list. With identifiers, returns full integration details including complete config with mapping configuration. Empty result is authoritative: no integrations are installed. Do not infer prior installation from existing blueprints. [API Reference](/api-reference/get-all-integrations) `test_integration_mapping` Test an integration mapping against raw data examples. Validates the mapping, checks for errors, and returns the mapped results or error messages. Pages & dashboards15 tools `delete_sidebar_folder`Beta Delete a folder from the sidebar. Pages and nested folders inside it are also deleted. [API Reference](/api-reference/pages) `delete_widget`Beta Delete a widget from a dashboard, home, or entity page by its ID. [API Reference](/api-reference/delete-a-widget) `duplicate_page`Beta Duplicate a dashboard page. Creates an identical copy with the same widgets, layout, page filters, icon, and description. Only dashboard pages are supported; entity pages cannot be duplicated. [API Reference](/api-reference/duplicate-a-page) `get_page`Beta Get a specific page by identifier. Returns full page details including widgets configuration. For entity pages (type "entity"), returns a tabs array with each tab's title, widgets, layout, and reserved flag. [API Reference](/api-reference/get-a-page) `get_sidebar`Beta Get the software catalog sidebar structure including all pages and folders. [API Reference](/api-reference/pages) `load_widget_schema`Beta Get the JSON schema for a specific dashboard widget type. Call this before upsert\_dashboard\_page, upsert\_widget, or update\_entity\_page to understand the required structure for the widget you want to create. `move_page`Beta Move a page to a different location in the sidebar. Use "parent" to place the page inside a folder (or null for sidebar root) and "after" to control its position within that level. [API Reference](/api-reference/patch-a-page) `update_entity_page`Beta Update an existing entity page's tabs, tab order, tab names, page metadata, or page filters. Entity pages are auto-generated per blueprint and cannot be created manually. Use upsert\_widget to update a single widget on a tab. [API Reference](/api-reference/pages) ▶Behavior tips ( 4 ) `upsert_dashboard_page`Beta Create or update a dashboard or home page with widgets, layout, and optional page filters. Do not use on entity pages; use update\_entity\_page or upsert\_widget instead. The dashboard-widget container is auto-generated. Use load\_widget\_schema to get the schema for a specific widget type before building widgets. [API Reference](/api-reference/pages) ▶Behavior tips ( 1 ) `upsert_sidebar_folder`Beta Create or update a folder in the sidebar. Folders can be nested by specifying a parent folder identifier. Use "after" to control position. [API Reference](/api-reference/pages) `upsert_widget`Beta Create or update a single widget on a dashboard, home, or entity page. Preferred for updating one widget instead of update\_entity\_page. For entity pages, tabTitle is required — call get\_page first to see available tabs. Call load\_widget\_schema first to get the full schema for the target widget type. [API Reference](/api-reference/create-a-widget) `list_plugins` List plugins uploaded to your organization (Port admin permissions required). Without identifiers, returns a summary list. With identifiers, returns full plugin details including metadata and custom parameters used by custom widgets. [API Reference](/api-reference/list-all-plugins-for-the-organization) `list_available_library_plugins` List plugins in the Port Plugins library - the curated catalog from the public port-plugins repository. Each entry includes readmeContent with catalog prerequisites. Call load\_skill("list-available-library-plugins") to browse, or load\_skill("install-plugin-library") before install flows. Not the same as list\_plugins (already installed in your org). [Documentation](/interface-builder/port-interface/plugins/.md#install-from-the-plugins-library) `install_library_plugin` Install a plugin from the Port Plugins library into your organization. Load install-plugin-library via load\_skill first, read readmeContent from list\_available\_library\_plugins, verify catalog prerequisites with list\_blueprints, confirm missing properties with the user, then upsert\_blueprint if needed before calling this tool. Returns alreadyInstalled: true if the plugin is already in your org. Do not use install\_integrations for library plugins. [API Reference](/api-reference/install-a-plugin-from-the-port-public-registry) [Documentation](/interface-builder/port-interface/plugins/.md#install-from-the-plugins-library) ▶Behavior tips ( 1 ) `upsert_plugin` Create or update a plugin for custom widgets (Port admin permissions required). Updates if the plugin identifier exists, creates if it does not. Use after you have a built plugin artifact and metadata (for example identifier, title, optional params). Not for Plugins library widgets - use install\_library\_plugin for those. [Documentation](/interface-builder/port-interface/plugins/.md#upload-custom-plugins) Scorecards3 tools `delete_scorecard` Delete scorecard by identifiers. [API Reference](/api-reference/delete-a-scorecard) `list_scorecards` List scorecards in your organization. Without identifiers, returns a summary list. With identifiers, returns full scorecard details including complete rule configurations. [API Reference](/api-reference/get-all-scorecards) `upsert_scorecard` Create or update a scorecard. Updates if the scorecard exists, creates if it does not. [API Reference](/api-reference/create-a-scorecard) Client Support Tool support in external MCP clients depends on whether the client implements the tools feature according to the [MCP specification](https://modelcontextprotocol.info/docs/clients/). Most MCP clients support tools. Check the [MCP client documentation](https://modelcontextprotocol.info/docs/clients/) to see which features your client supports. ## Select which tools to use[​](#select-which-tools-to-use "Direct link to Select which tools to use") By default, when you open a chat with Port MCP, all available tools (based on your permissions) are loaded and ready to use. However, you can customize which tools are available if you want to focus on specific workflows. For example, if you only want to query data from Port without building or modifying anything, you can limit the tools to just the read-only ones. This helps reduce complexity and ensures you don't accidentally make changes. ### Using headers to control tool availability[​](#using-headers-to-control-tool-availability "Direct link to Using headers to control tool availability") You can use headers when configuring your MCP server connection to control which tools are available: **Read-only mode** - The `x-read-only-mode` header controls whether write tools are available. Set it to `1` to restrict the MCP server to only expose read-only tools, completely hiding write tools from the available tools list. Set it to `0` (default) to allow all tools based on your permissions. **Control actions** - The `x-allowed-actions-to-run` header controls which actions are available through the `run_action` tool. This header accepts a comma-separated list of action identifiers. Only the specified actions will be available. If the header is not specified, all actions you have permission to run will be available. If set to an empty string, no actions will be allowed to run. Examples: * `x-allowed-actions-to-run: "create_github_issue,create_incident"` - Allows only the specified GitHub and incident actions. * `x-allowed-actions-to-run: "create_service,deploy_to_production"` - Allows only the specified deployment actions. * `x-allowed-actions-to-run: ""` - No actions will be allowed to run. See the [token-based authentication guide](/agent-management/port-mcp-server/token-based-authentication/.md) for examples of how to configure these headers. ### Using client UI to select tools[​](#using-client-ui-to-select-tools "Direct link to Using client UI to select tools") * Cursor * VSCode * Claude In Cursor, you can customize which tools are available through the UI after connecting to Port MCP. Once connected, you can select specific tools as shown below. ![Enabling specific tools in Cursor](/assets/images/MCPCursorEnableTools-3c7bcb8970cfc1e209dde52318ba2197.png) In VSCode, you can choose the tools through the UI after connecting to Port MCP. ![Enabling specific tools in VSCode](/assets/images/MCPVSCodeEnableTools-2a4ce11acae848bf33b52e138e21845e.png) When creating a custom connector in Claude, you can specify exactly which tools to expose instead of enabling everything. ![Enabling specific tools in Claude](/assets/images/MCPClaudeEnableTools-488b41f87c50aad0004f5d2839a4fe46.png) Refer to the [Claude custom connector documentation](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) for detailed instructions. --- # Installation No Port AI terms required The Port MCP server does **not** use Port AI capabilities. You can install and use it without enabling Port AI features or accepting the [Port AI Terms](https://www.port.io/legal/ai-terms). This section details the installation process of the Port MCP server for your preferred IDE or AI assistant. Use Port's **remote MCP server** for new connections. The Docker image and `uvx mcp-server-port` package from the [deprecated local MCP server repository](https://github.com/port-labs/port-mcp-server) are no longer maintained. If you used that server, remove its entry from your client's MCP configuration and follow the setup below. Select your client and Port region, then follow one setup path. Use the EU endpoint (`https://mcp.port.io/v1`) for `app.port.io`, or the US endpoint (`https://mcp.us.port.io/v1`) for `app.us.port.io`. Cursor, VS Code, GitHub Copilot CLI, and Claude Code can connect directly over HTTP. GitHub Copilot Chat in VS Code uses the VS Code connection. The `mcp-remote` package is a local compatibility proxy for clients that need it; it connects to the same remote Port server and is separate from the deprecated Docker/`uvx` server. * Cursor * VS Code * GitHub Copilot UI * GitHub Copilot CLI * Claude AI * Claude Code (CLI) * Codex CLI To connect Cursor to Port's remote MCP, follow these steps: For the EU region, you can use the [Cursor install link](cursor://anysphere.cursor-deeplink/mcp/install?name=port\&config=eyJ1cmwiOiJodHRwczovL21jcC5wb3J0LmlvL3YxIn0=) and continue with authentication in step 3. For the US region, or to configure the connection manually, follow all steps below. 1. **Open Cursor settings** In the settings, click **Customize**, select **MCPs**, and then click **New MCP Server**. ![Cursor Customize settings with MCPs selected and New MCP Server highlighted](/img/ai-agents/MCPInstallCursorStep1.png) 2. **Configure the MCP server** Add the following configuration: ``` { "mcpServers": { "port-eu": { "url": "https://mcp.port.io/v1", "headers": { "x-read-only-mode": "0" } } } } ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. ![Cursor mcp.json defining a port-remote server with the Port US MCP URL](/img/ai-agents/MCPInstallCursorStep2.png) Read-only mode The `x-read-only-mode` header defaults to `0`, which allows all tools based on your permissions. You can change it to `1` to restrict the MCP server to only expose read-only tools. When set to `1`, write tools are completely hidden from the available tools list, ensuring you can only query data without making modifications. 3. **Authenticate with Port** Click on **"Needs login"** and complete the authentication flow in the window that opens. ![Cursor MCP Tools settings showing port-remote enabled with Needs login status](/img/ai-agents/MCPInstallCursorStep3.png) Cursor OAuth session persistence Port MCP authentication works on the initial connection. If Cursor prompts you to re-authenticate every day or two, this is typically caused by a known Cursor OAuth issue rather than a Port-side session or configuration problem. When Cursor refreshes your OAuth session, it may fail to persist or update tokens correctly. This is especially common when you have multiple Cursor windows open. Port's logs may show `invalid_grant` errors when Cursor attempts to refresh with a stale token. For more details and workarounds, see these Cursor community discussions: * [Cursor does not refresh OAuth access tokens for MCP servers](https://forum.cursor.com/t/cursor-does-not-refresh-oauth-access-tokens-for-mcp-servers/160637). * [OAuth MCPs still require daily re-auth on 3.3.30, multi-window token store not synced](https://forum.cursor.com/t/oauth-mcps-still-require-daily-re-auth-on-3-3-30-multi-window-token-store-not-synced/160618). * [Cursor OAuth MCPs need to connect frequently](https://forum.cursor.com/t/cursor-oauth-mcps-need-to-connect-fequently/147067). Cursor has reported fixes for some MCP OAuth issues in stable releases after May 12, 2026. If you experience frequent re-authentication, update Cursor to the latest stable version and check whether the issue persists. 4. **Verify connection** After successful authentication, you'll see the list of available tools from the MCP server. ![Connected port-remote server in Cursor listing available blueprint, entity, scorecard, and action tools](/img/ai-agents/MCPInstallCursorStep4.png) Connect VS Code directly to Port's remote MCP server over HTTP. Use a current VS Code version with MCP access enabled by your organization. This setup does not require Node.js or `mcp-remote`. See the [VS Code MCP documentation](https://code.visualstudio.com/docs/agent-customization/mcp-servers) for client requirements. ## Install via the extensions tab Install Port from the MCP gallery: 1. Open the **Extensions** view. 2. Search for `@mcp Port`. 3. Select **Port** by **port-labs**, then click **Install**. ![VS Code MCP gallery showing the Port server after a search for @mcp Port](/img/agent-management/port-mcp-vscode.png) 4. If VS Code asks you to choose a server URL, pick the endpoint for your Port organization. Use `https://mcp.port.io/v1` for the EU region (`app.port.io`), or `https://mcp.us.port.io/v1` for the US region (`app.us.port.io`). 5. Complete the browser authentication flow. 6. Verify that VS Code displays the available tools. Open a chat and select Port tools from the tool picker. Try asking, "List my Port blueprints." The gallery install allows tools based on your Port permissions. To expose only read-only tools, or to set other headers, add the server manually. ## Install manually 1. Open the **Command Palette** and run **MCP: Open User Configuration**. 2. Add the following configuration to `mcp.json`. If you already have a `servers` object, add the Port entry to it: ``` { "servers": { "port-vscode-eu": { "type": "http", "url": "https://mcp.port.io/v1", "headers": { "x-read-only-mode": "0" } } } } ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. Set `x-read-only-mode` to `1` if you want to expose only read-only tools. The default, `0`, allows tools based on your Port permissions. 3. Save the file, click **Start** above the Port server entry, and complete the browser authentication flow. 4. Verify that VS Code displays the available tools. Open a chat and select Port tools from the tool picker. Try asking, "List my Port blueprints." If the connection fails, run **MCP: List Servers**, select your Port server, and choose **Show Output** to inspect the error. Check that your configuration uses the correct regional endpoint and that you completed browser authentication. GitHub Copilot Chat in VS Code uses the same Port connection as the **VS Code** tab, including the MCP gallery install. Complete that setup, then enable the Port tools in Copilot Chat. 1. Complete the **VS Code** tab steps, including browser authentication. 2. Open Copilot Chat and switch the mode to **Agent**. 3. In the tools picker, enable the Port tools. 4. To test the installation, ask Copilot, "List my Port blueprints." Organization policy On Copilot Business and Copilot Enterprise, an organization owner must set **MCP servers in Copilot** to **Enabled** before Copilot Chat can call MCP tools. This policy is disabled by default. Copilot Free, Copilot Pro, Copilot Pro+, and Copilot Max are not covered by it. If the organization limits MCP access to registry servers, Port must be listed in that registry. See [managing policies for GitHub Copilot in your organization](https://docs.github.com/en/copilot/how-tos/administer-copilot/manage-for-organization/manage-policies) and [configure MCP server access](https://docs.github.com/en/copilot/how-tos/administer-copilot/manage-mcp-usage/configure-mcp-server-access). Copilot Chat in Visual Studio and JetBrains uses each product's own MCP screen. For those editors, follow [extending GitHub Copilot Chat with MCP](https://docs.github.com/en/copilot/customizing-copilot/using-model-context-protocol/extending-copilot-chat-with-mcp) and use the Port URL from the **VS Code** tab. Connect GitHub Copilot CLI directly to Port's remote MCP server over HTTP. See the [GitHub Copilot CLI MCP documentation](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers) for client requirements. 1. Add the following configuration to `mcp-config.json`. If you already have an `mcpServers` object, add the Port entry to it: ``` { "mcpServers": { "port-copilot-eu": { "tools": ["*"], "type": "http", "url": "https://mcp.port.io/v1", "headers": { "x-read-only-mode": "0" } } } } ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. Set `x-read-only-mode` to `1` if you want to expose only read-only tools. The default, `0`, allows tools based on your Port permissions. OAuth redirect port GitHub Copilot CLI uses CIMD (Client ID Metadata) authorization and expects OAuth callbacks on port `33418`. If you get an error like `unauthorized_client: Callback URL mismatch`, add an `auth` block with `redirectPort` set to `33418` inside your Port server entry: ``` "auth": { "redirectPort": 33418 } ``` 2. Start Copilot CLI, run `/mcp`, select your Port server, and complete the browser authentication flow. 3. Verify that Copilot CLI displays the available Port tools. Try asking, "List my Port blueprints." If authentication fails, check that your configuration uses the correct regional endpoint and that you completed browser authentication. To connect Claude AI (Desktop & Web) to Port's remote MCP, install the Port official connector from the Claude connectors library. For detailed instructions, refer to the [official Anthropic documentation on custom connectors](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp). * **Direct install link**: [Install Port connector in Claude](https://claude.ai/directory/8f5edd1c-c876-465e-a5b9-cc8e6d27dcb7). 1. **Open [Port Connector page](https://claude.ai/directory/8f5edd1c-c876-465e-a5b9-cc8e6d27dcb7)** by visiting this link or searching for "Port" connector, and click **Connect**. 2. **Enter your MCP server URL** when prompted: ``` https://mcp.port.io/v1 ``` US region If your Port organization is in the US region, use `https://mcp.us.port.io/v1` instead. ![Claude Connectors library search results with the Port IO connector selected](/img/ai-agents/MCPClaudeConnectorLibrary.png) Connect Claude Code directly to Port's remote MCP server using HTTP. This is the recommended setup and does not require Node.js, `npx`, or a local MCP server. **Prerequisites** * Claude Code CLI installed on your machine. For installation instructions, refer to the [official Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/getting-started). * Access to your Port account in the browser for OAuth authentication. **Add Port MCP** Run the following command from the directory where you use Claude Code: ``` claude mcp add --transport http port-eu https://mcp.port.io/v1 --header "x-read-only-mode: 0" ``` If your Port organization is in the US region, replace `port-eu` and `https://mcp.port.io/v1` with `port-us` and `https://mcp.us.port.io/v1`. Read-only mode The `x-read-only-mode` header defaults to `0`, which allows all tools based on your permissions. Change it to `1` to restrict the MCP server to only expose read-only tools. When set to `1`, write tools are completely hidden from the available tools list, ensuring you can only query data without making modifications. **Verify the connection** Start Claude Code: ``` claude ``` Run `/mcp`, select **port-eu** or **port-us**, and follow the browser authentication flow. After authentication, verify that the server shows its available tools. Try asking, "List my Port blueprints." **Resolve connection timeouts** If you previously used `claude mcp add-json` with `npx mcp-remote`, that configuration starts a local proxy. On first use, `npx` may need to download the package and its dependencies, adding startup time. The proxy also waits for browser authentication. Package registry access, proxy settings, or an incomplete login can cause this path to time out even when direct HTTP works. Remove the old Port server entry from your MCP configuration before adding the direct HTTP connection above. If the direct connection also times out, check your region, access to the MCP endpoint from your terminal, and completion of the browser login. Use `/mcp` to inspect the connection status. See the [Claude Code MCP documentation](https://code.claude.com/docs/en/mcp) for connection diagnostics and startup timeout settings. ![Claude Code session using Port MCP tools to list service entities and production readiness scorecards](/img/ai-agents/MCPClaudeCodeSetup.png) To connect OpenAI Codex CLI to Port's remote MCP server, install Codex CLI locally and add Port as a remote MCP server. For complete instructions, refer to the [official Codex CLI documentation](https://developers.openai.com/codex/cli). **Prerequisites** * An OpenAI account with access to Codex CLI. * The Codex CLI installed on your machine. * Access to your Port account in the browser for OAuth authentication. * Your Port region. If your Port app URL contains `us`, use the US MCP URL. Otherwise, use the EU MCP URL. * [Node.js](https://nodejs.org/) installed on your machine. **Step 1: Install Codex CLI** Run the following command to install Codex CLI: ``` npm install -g @openai/codex ``` After installation, verify that Codex CLI is available: ``` codex --version ``` **Step 2: Sign in to Codex** Start Codex and complete the sign-in flow: ``` codex ``` **Step 3: Add Port MCP** Run the following command: ``` codex mcp add port-eu --url https://mcp.port.io/v1 ``` US region If your Port organization is in the US region, replace `port-eu` and `https://mcp.port.io/v1` with `port-us` and `https://mcp.us.port.io/v1`. **Step 4: Authenticate with Port** After adding the server, authenticate using OAuth. Run the matching login command for the region you used in step 3: ``` codex mcp login port-eu ``` If you used `port-us` in step 3, run `codex mcp login port-us` instead. Complete the Port authentication flow in the browser window that opens. **Step 5: Verify the connection** Start a Codex session and run `/mcp` and type port-eu or port-us to verify that the Port MCP server is connected and showing its tools. ![Codex session confirming the port-eu connection and listing available Port MCP tools by category](/img/ai-agents/MCPCodexSetup.png) **Step 6: Query Port through Codex** Once connected, you can interact with your Port catalog using natural language. Codex will automatically use the Port MCP tools to fulfill your requests. Try these example prompts: * "Show me all services owned by the backend team" * "What scorecards are failing for my production services?" * "Who is the owner of the payments service?" ![Codex using Port MCP tools to evaluate passing scorecards for the simple\_bank service](/img/ai-agents/MCPCodexQuery.png) **Optional: Configure Codex manually** You can also add the MCP server directly to your `~/.codex/config.toml` file: ``` [mcp_servers.port-eu] url = "https://mcp.port.io/v1" enabled = true ``` US region If your Port organization is in the US region, replace `port-eu` and `https://mcp.port.io/v1` with `port-us` and `https://mcp.us.port.io/v1`. After editing the file, run the matching `codex mcp login` command from step 4. Read-only mode You can restrict Codex to read-only Port tools by adding the `x-read-only-mode` header to your Codex MCP configuration. See the [MCP server headers](https://docs.port.io/agent-management/port-mcp-server/installation#mcp-server-headers) section for the full configuration. Extended session duration MCP clients can maintain authenticated sessions for up to 30 days (as long as you're not idle for 15 days). This applies to OAuth-based authentication for interactive use, providing a more seamless experience for long-running integrations. ## Connect the server to multiple organizations[​](#connect-the-server-to-multiple-organizations "Direct link to Connect the server to multiple organizations") Port uses your browser's OAuth session to approve MCP connections. When your MCP client opens the authentication prompt, you approve access in the organization where you are currently logged in. Follow these steps to connect to the correct organization: * Make sure you are logged in to the desired organization in your browser before you start the MCP connection flow. * Approve the OAuth prompt from your MCP client while you remain logged in to that organization. * Continue using the MCP client; changing your browser session afterward does not change the connected organization. To connect another organization from the same MCP client, add a second configuration and repeat the flow while logged in to the other organization. Each configuration keeps its own OAuth approval, so you can work with multiple organizations in parallel. External tools via MCP connectors In addition to Port's native tools, you can also access external MCP servers (like Notion, Slack, Jira, and more) through the same interface. If you have configured [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md), authenticate to them in Port and they will automatically be available in your IDE alongside Port's native capabilities. Platform teams can also run an [MCP registry](/agent-management/ai-registry/mcp-registry/.md) in the software catalog to track which MCP servers are approved, let developers request new servers, and publish installation guidance. ## Connecting the server when SSO is enabled[​](#connecting-the-server-when-sso-is-enabled "Direct link to Connecting the server when SSO is enabled") If your organization uses SSO (Single Sign-On) and you see an error like the one below when trying to connect to the MCP Server: ![Port error page showing Oops something went wrong when connecting to MCP server](/img/ai-agents/PortAIMCPServerSSOEreror.png) This error occurs because the SSO connection needs to be configured for domain-level authentication to work with the MCP Server's OAuth flow. *Why this happens* - When SSO is initially configured in Port, the authentication connection starts as a standard type. For the MCP Server to authenticate users through SSO, the connection needs to be upgraded to "domain level" mode, which enables Dynamic Client Registration (DCR). This configuration change can only be made by Port on the backend. *How to resolve* - Contact our support team and let them know you're experiencing an SSO authentication error when connecting to the MCP Server. The support team will update your SSO connection configuration to enable domain-level authentication, which will allow the MCP Server OAuth flow to work correctly with your SSO provider. Once the configuration is updated, retry the MCP Server connection and the authentication should work as expected. ## Token-based authentication[​](#token-based-authentication "Direct link to Token-based authentication") You can also connect using machine authentication for automated environments like CI/CD pipelines where interactive authentication isn't possible. Request a token from the MCP OAuth endpoint: ``` curl -X POST "https://mcp.port.io/v1/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" ``` For complete examples and detailed setup instructions, see our [machine authentication guide](/agent-management/port-mcp-server/token-based-authentication/.md). ## MCP server headers[​](#mcp-server-headers "Direct link to MCP server headers") The Port MCP Server supports several headers that allow you to customize its behavior: | Header | Type | Default | Description | | -------------------------- | ------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `x-read-only-mode` | String | `0` | Controls whether write tools are available. Set to `1` to restrict the MCP server to only expose read-only tools, completely hiding write tools from the available tools list. Set to `0` to allow all tools based on your permissions. | | `x-allowed-actions-to-run` | String | All actions | Comma-separated list of action identifiers that controls which actions are available through the `run_action` tool. Only the specified actions will be available. If not specified, all actions you have permission to run will be available. If set to an empty string, no actions will be allowed to run. Example: `"create_github_issue,create_incident"`. | ### Configuring headers in your MCP client[​](#configuring-headers-in-your-mcp-client "Direct link to Configuring headers in your MCP client") Headers are set in your MCP client's configuration file, not in Port itself. The exact syntax depends on your client. * Claude Desktop * Claude Code (CLI) * Cursor * VS Code * GitHub Copilot CLI * Codex CLI Use the official Port connector for the standard Claude Desktop setup above. If you need to pass custom headers through a local proxy, configure `mcp-remote` in `claude_desktop_config.json` as shown below. This option requires [Node.js](https://nodejs.org/) and access to the npm registry. It connects to Port's remote MCP server. ``` { "mcpServers": { "port": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.port.io/v1", "--header", "x-read-only-mode: 1", "--header", "x-allowed-actions-to-run: create_github_issue,deploy_service" ] } } } ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. Pass headers using the `--header` flag when adding the direct HTTP connection. Each header requires a separate `--header` argument. If you already added `port-eu`, update that entry's headers in your MCP configuration instead of adding it again: ``` claude mcp add --transport http port-eu https://mcp.port.io/v1 \ --header "x-read-only-mode: 1" \ --header "x-allowed-actions-to-run: create_github_issue,deploy_service" ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. Add a `headers` block to your `.cursor/mcp.json` file: ``` { "mcpServers": { "port": { "url": "https://mcp.port.io/v1", "headers": { "x-read-only-mode": "1", "x-allowed-actions-to-run": "create_github_issue,deploy_service" } } } } ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. Add a `headers` block to your `.vscode/mcp.json` file: ``` { "servers": { "port": { "url": "https://mcp.port.io/v1", "headers": { "x-read-only-mode": "1", "x-allowed-actions-to-run": "create_github_issue,deploy_service" } } } } ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. Add a `headers` block to your `mcp-config.json` file: ``` { "mcpServers": { "port-copilot-eu": { "tools": ["*"], "type": "http", "url": "https://mcp.port.io/v1", "headers": { "x-read-only-mode": "1" } } } } ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. Add an `http_headers` block to your `~/.codex/config.toml` file: ``` [mcp_servers.port] url = "https://mcp.port.io/v1" enabled = true [mcp_servers.port.http_headers] x-read-only-mode = "1" x-allowed-actions-to-run = "create_github_issue,deploy_service" ``` US region If your Port organization is in the US region, replace `https://mcp.port.io/v1` with `https://mcp.us.port.io/v1`. Restricting to specific actions Use `x-allowed-actions-to-run` to scope a connection to only the actions relevant for a given context - for example, allowing a developer's IDE session to run only deployment actions, or giving a CI/CD integration access to a single scaffold action. To find an action's identifier, open the [self-service](https://app.port.io/self-serve) page and copy the identifier from the action. ## Prompts[​](#prompts "Direct link to Prompts") The MCP server exposes static and custom [prompts](/agent-management/ai-registry/prompts/.md) in supported MCP clients. For setup, the prompt blueprint, and using prompts in Port AI Assistant, see the [Prompts overview](/agent-management/ai-registry/prompts/.md). ## Connecting to AI Agents[​](#connecting-to-ai-agents "Direct link to Connecting to AI Agents") To connect the Port MCP server to AI agents in CI/CD environments or other automated contexts where interactive authentication isn't possible, see our [machine authentication](/agent-management/port-mcp-server/token-based-authentication/.md) guide. ## FAQ and troubleshooting[​](#faq-and-troubleshooting "Direct link to FAQ and troubleshooting") 1What happens if I approve the OAuth prompt in the wrong organization? 2Why does Cursor prompt me to re-authenticate every day or two? 3How do I connect from Augment Code if OAuth fails? 4Why do MCP tools fail behind a corporate VPN or firewall? 5How can I disable the Port MCP installation option? --- # Port MCP server No Port AI terms required The Port MCP server does **not** use Port AI capabilities. You can install and use it without enabling Port AI features or accepting the [Port AI Terms](https://www.port.io/legal/ai-terms). [YouTube video player](https://www.youtube.com/embed/WrVgQ-whBiE)
The Port Model Context Protocol (MCP) Server acts as a bridge, enabling Large Language Models (LLMs) - like those powering Claude, Cursor, or GitHub Copilot - to interact directly with your Port.io developer portal. This allows you to leverage natural language to query your software catalog, analyze service health, manage resources, and even streamline development workflows, all from your preferred interfaces. Port MCP server vs. Port AI The Port MCP server is separate from [Port AI](/port-ai/overview/.md). Your IDE or chat client runs the LLM; Port only exposes catalog tools. ## Why integrate LLMs with your developer portal?[​](#why-integrate-llms-with-your-developer-portal "Direct link to Why integrate LLMs with your developer portal?") The primary advantage of the Port MCP Server is the ability to bring your developer portal's data and actions into the conversational interfaces you already use. This offers several benefits: * **Reduced context switching:** Access Port information and initiate actions without leaving your IDE or chat tool. * **Increased efficiency:** Get answers and perform tasks faster using natural language commands. * **Improved developer experience:** Make your developer portal more accessible and intuitive to interact with. * **Enhanced data-driven decisions:** Easily pull specific data points from Port to inform your work in real-time. As one user put it: > "It would be interesting to build a use case where a developer could ask Copilot from his IDE about stuff Port knows about, without actually having to go to Port." The Port MCP Server directly enables these kinds of valuable, in-context interactions. ## Key capabilities and use-cases[​](#key-capabilities-and-use-cases "Direct link to Key capabilities and use-cases") [YouTube video player](https://www.youtube.com/embed/hxUTTPSApQs) The Port MCP Server enables you to interact with your Port data and capabilities directly through natural language within your chosen LLM-powered tools. Here's what you can achieve: ### Find information quickly[​](#find-information-quickly "Direct link to Find information quickly") Effortlessly query your software catalog and get immediate answers. This eliminates the need to navigate through UIs or write complex API queries when you need information. * Ask: "Who is the owner of service X?" * Ask: "How many services do we have in production?" * Ask: "Show me all the microservices owned by the Backend team." * Ask: "What are the dependencies of the 'OrderProcessing' service?" ![GitHub Copilot querying ECS services via Port MCP](/img/ai-agents/MCPCopilotAskServices.png) ### Vibe-build in Port[​](#vibe-build-in-port "Direct link to Vibe-build in Port") Leverage Claude's capabilities to manage and build your entire Port software catalog. You can create and configure blueprints, set up self-service actions, design scorecards, and more. * Follow the [Build and configure Port using the MCP server](/guides/all/build-port-with-mcp/.md) guide for complete data model, action, automation, and scorecard examples. * Ask: "Please help me apply this guide into my Port instance - \[\[guide URL]]" * Ask: "I want to start managing my k8s deployments, how can we build it in Port?" * Ask: "I want a new production readiness scorecard to track the code quality and service alerts" * Ask: "Create a new self-service action in Port to scaffold a new service" ![Claude creating Port self-service action via MCP](/img/ai-agents/MCPClaudeBuildSSA.png) ### Analyze scorecards and quality[​](#analyze-scorecards-and-quality "Direct link to Analyze scorecards and quality") Gain insights into service health, compliance, and quality by leveraging Port's scorecard data. Identify areas for improvement and track progress against your standards. * Ask: "Which services are failing our security requirements scorecard?" * Ask: "What's preventing the 'InventoryService' from reaching Gold level in the 'Production Readiness' scorecard?" * Ask: "Show me the bug count vs. test coverage for all Java microservices." ![Claude microservice bug count vs test coverage](/img/ai-agents/MCPClaudeInsightsBugs.png) * Ask: "Which of our services are missing critical monitoring dashboards?" ![Claude services monitoring dashboard coverage analysis](/img/ai-agents/MCPClaudeMonitoringServices.png) ### Streamline development and operations[​](#streamline-development-and-operations "Direct link to Streamline development and operations") Receive assistance with common development and operational tasks, directly within your workflow. * Ask: "What do I need to do to set up a new 'ReportingService'?" * Ask: "Guide me through creating a new component blueprint with 'name', 'description', and 'owner' properties." * Ask: "Help me add a rule to the 'Tier1Services' scorecard that requires an on-call schedule to be defined." ![Claude DataProcessor service setup and dependencies](/img/ai-agents/MCPClaudeServiceSetup.png) ### Find your own use cases[​](#find-your-own-use-cases "Direct link to Find your own use cases") You can use Port's MCP to find the use cases that will be valuable to you. Try using this prompt: "think of creative prompts I can use to showcase the power of Port's MCP, based on the data available in Port" ## FAQ[​](#faq "Direct link to FAQ") 1Who runs the LLM when I use the Port MCP server? 2Which permissions control Port MCP server access? 3What is the difference between the Port MCP server and Port AI? ## Getting started[​](#getting-started "Direct link to Getting started") Ready to connect your IDE or AI assistant to Port? See the [installation guide](/agent-management/port-mcp-server/installation/.md) for step-by-step setup instructions for Cursor, VS Code, GitHub Copilot, GitHub Copilot CLI, Claude, Codex CLI, and more. For CI/CD pipelines and other automated environments, see [machine authentication](/agent-management/port-mcp-server/token-based-authentication/.md). --- # Machine authentication for the Port MCP server When you connect the Port MCP server from CI/CD pipelines, GitHub Actions, or other automated environments, interactive OAuth (browser login) is not practical. Port supports **machine-to-machine authentication** using your existing Port **client ID** and **client secret** through the MCP OAuth token endpoint. ## Authentication methods compared[​](#authentication-methods-compared "Direct link to Authentication methods compared") | Method | Best for | Long-lived secret | User consent | | -------------------------------------------------- | ------------------------------------ | ------------------------------- | ------------ | | Interactive OAuth (`authorization_code` + refresh) | Cursor, VS Code, Claude Desktop | Refresh token (up to \~30 days) | Yes | | OAuth `client_credentials` (MCP token endpoint) | CI/CD, headless agents, `mcp-remote` | Port client ID + secret | No | Long-lived vs short-lived credentials Your **client ID** and **client secret** are long-lived. The **access token** they produce is short-lived (typically about 3 hours). Re-exchange your credentials when the token expires. There is no refresh token for the `client_credentials` grant. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") 1. Create a Port **client ID** and **client secret** in your Port organization settings. 2. Use a dedicated service account credential for automation, not a personal user's credentials. 3. Store secrets in your CI secret manager. Never commit them to source control. ## Request an access token[​](#request-an-access-token "Direct link to Request an access token") Port's MCP server exposes a standards-compliant OAuth 2.0 token endpoint that accepts the `client_credentials` grant. It returns a machine JWT (`isMachine: true`) that you pass as a Bearer token when connecting to MCP. ### Discover supported grants[​](#discover-supported-grants "Direct link to Discover supported grants") ``` curl -s "https://mcp.port.io/.well-known/oauth-authorization-server" | jq . ``` The response includes: * `token_endpoint`: `https://mcp.port.io/v1/token`. * `grant_types_supported`: `authorization_code`, `refresh_token`, `client_credentials`. * `token_endpoint_auth_methods_supported`: `none`, `client_secret_post`, `client_secret_basic`. US region For US-hosted Port, replace `mcp.port.io` with `mcp.us.port.io`. ### Request a token (`client_secret_post`)[​](#request-a-token-client_secret_post "Direct link to request-a-token-client_secret_post") ``` curl -s -X POST "https://mcp.port.io/v1/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" ``` Example response: ``` { "access_token": "eyJ...", "token_type": "Bearer", "expires_in": 10800 } ``` ### Request a token (`client_secret_basic`)[​](#request-a-token-client_secret_basic "Direct link to request-a-token-client_secret_basic") ``` curl -s -X POST "https://mcp.port.io/v1/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \ -d "grant_type=client_credentials" ``` ### Connect with the token[​](#connect-with-the-token "Direct link to Connect with the token") Most automated setups use `mcp-remote` with a static Bearer header. Request a token from `/v1/token`, then pass it when connecting: ``` npx -y mcp-remote "https://mcp.port.io/v1" \ --header "Authorization: Bearer YOUR_ACCESS_TOKEN" \ --header "x-read-only-mode: 0" ``` If your MCP client supports OAuth machine-to-machine authentication natively, point it at the MCP server URL and let it handle token exchange against `/v1/token` using your Port client ID and secret. Token lifecycle and rate limits Access tokens expire (typically about 3 hours). Request a new token from `/v1/token` before expiry, or on `401` responses. Do not treat the access token itself as a long-lived secret. The `client_credentials` grant is also rate-limited to 50 requests per 15 minutes per client. Cache tokens until they are close to expiry instead of requesting a new token on every MCP call. ## Example: Claude Code in GitHub Actions[​](#example-claude-code-in-github-actions "Direct link to Example: Claude Code in GitHub Actions") Here is a complete example showing how to connect Claude Code to the Port MCP server within a GitHub Actions workflow: **Show example workflow (click to expand)** .github/workflows/claude-code-mcp.yml ``` name: Port MCP Server Demo with Claude Code on: workflow_dispatch env: PORT_MCP_URL: ${{ vars.PORT_MCP_URL }} jobs: demo: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v6 - name: Authenticate with Port id: port-auth run: | response=$(curl -s -X POST "${{ env.PORT_MCP_URL }}/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=${{ secrets.PORT_CLIENT_ID }}" \ -d "client_secret=${{ secrets.PORT_CLIENT_SECRET }}") token=$(echo "$response" | jq -r '.access_token') echo "::add-mask::$token" echo "access_token=$token" >> "$GITHUB_OUTPUT" - name: Claude Code against Port MCP uses: anthropics/claude-code-action@beta with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} mode: agent mcp_config: | { "mcpServers": { "port-prod": { "command": "npx", "args": [ "mcp-remote", "${{ env.PORT_MCP_URL }}", "--header", "Authorization: Bearer ${{ steps.port-auth.outputs.access_token }}", "--header", "x-read-only-mode: 0" ] } } } allowed_tools: "mcp__port-prod__list_blueprints,mcp__port-prod__get_entities" direct_prompt: | List all blueprints, then show entities of the "zendesk_ticket" blueprint. ``` ### How this workflow works[​](#how-this-workflow-works "Direct link to How this workflow works") 1. **Authentication** - The `port-auth` step exchanges your Port client credentials for a short-lived access token via `POST /v1/token` with `grant_type=client_credentials`. 2. **MCP connection** - Claude Code connects to the remote MCP server using the `mcp-remote` package, passing the access token in the `Authorization` header. 3. **Read-only mode** - The `x-read-only-mode` header defaults to `0`, which allows all tools based on your permissions. You can change it to `1` to restrict the MCP server to only expose read-only tools, completely hiding write tools from the available tools list. 4. **Action filtering** - The `x-allowed-actions-to-run` header (optional) allows you to control which actions are available through the `run_action` tool. It accepts a comma-separated list of action identifiers. For example, `x-allowed-actions-to-run: "create_github_issue,create_incident"` restricts access to only the specified actions. 5. **Tool access** - Claude Code can invoke only the specific Port tools listed in `allowed_tools`, ensuring controlled access to your Port instance. 6. **Execution** - The AI agent executes the provided prompt using the available Port tools to query your software catalog. Customize your integration For read-only workflows, you can change the `x-read-only-mode` header from `0` to `1` to restrict tools to read-only operations, or limit `allowed_tools` to just the query operations you need. To control which actions are available, use the `x-allowed-actions-to-run` header with a comma-separated list of action identifiers. For example: * `x-allowed-actions-to-run: "create_github_issue,merge_github_pr"` - Only the specified GitHub actions. * `x-allowed-actions-to-run: "create_incident,send_notification"` - Only incident and notification actions. Choose the appropriate MCP URL for your Port region (EU: `https://mcp.port.io/v1`, US: `https://mcp.us.port.io/v1`). ### Adapting for other AI agents[​](#adapting-for-other-ai-agents "Direct link to Adapting for other AI agents") While this example focuses on Claude Code, the same authentication pattern can be applied to other AI agents and platforms: 1. **Obtain Port credentials** - Create a client ID and secret in your Port dashboard. 2. **Generate access token** - Request a token from `POST /v1/token` with `grant_type=client_credentials`. 3. **Configure MCP connection** - Point your AI agent to the remote MCP server with the token. 4. **Define tool permissions** - Specify which Port tools the AI agent can access. 5. **Execute workflows** - Let the AI agent interact with your Port data and capabilities. ## When to use which method[​](#when-to-use-which-method "Direct link to When to use which method") * **Local IDE with browser login** - Use interactive OAuth from the [installation guide](/agent-management/port-mcp-server/installation/.md). * **CI/CD or headless agents** - Request a token from `POST /v1/token` with `grant_type=client_credentials`, then pass it as a Bearer token. * **MCP clients with native OAuth M2M support** - Configure the client to use the MCP server OAuth metadata so it handles token lifecycle automatically. ## Port API compatibility[​](#port-api-compatibility "Direct link to Port API compatibility") If you already obtain a machine token via the Port REST API (`POST /v1/auth/access_token`), you can use that same Bearer token with the MCP server. The MCP token endpoint returns the same JWT under the hood. This is useful when a pipeline already authenticates against the Port API for other calls and you want to reuse the same token pattern. For new MCP integrations, prefer `POST /v1/token` so your auth flow stays within the MCP OAuth surface and supports discovery via `.well-known/oauth-authorization-server`. ## Security best practices[​](#security-best-practices "Direct link to Security best practices") * Use a dedicated automation credential with the minimum permissions needed. * Rotate client secrets if they are exposed. * Mask tokens in CI logs (for example, `::add-mask::` in GitHub Actions). * Prefer `x-read-only-mode: 1` for read-only automation. * Use `x-allowed-actions-to-run` to restrict which actions `run_action` can trigger. --- # Visualize organization hierarchy This guide demonstrates how to: * Model the hierarchy tiers in your company. * Create dedicated views for each tier, displaying relevant information. ## Data model overview[​](#data-model-overview "Direct link to Data model overview") Port allows you to model your team structure by connecting teams in a hierarchy using a single **Team** blueprint: * **Team** represents any level in the hierarchy, from high-level groups to leaf-level teams that directly own resources. * **Service** is used as an example throughout this guide for an entity owned by a team. Any blueprint that connects to teams (repositories, cloud resources, projects, etc.) can serve this role. ### Example hierarchies[​](#example-hierarchies "Direct link to Example hierarchies") The simplest hierarchy has two levels: ![Team hierarchy diagram showing group at the top, then team, then service at the bottom](/img/guides/hierarchyTiers/team_hierarchy.png)

You can add more levels to match your needs by adding more enum values to the `type` property of the **Team** blueprint (which we will set up in the [section below](#extend-the-team-blueprint)). Throughout this guide, we will use `Group > Team` as our example hierarchy, with **Service** as an example of an entity owned by a team, but any blueprint that connects to teams can play this role. Other possible hierarchies could look like the following: * `Department > Domain > Tribe > Squad > Service` * `Department > Group > Unit > Team > Service` Since every level is just a team entity with a `parent_team` relation, the setup works the same regardless of how many levels you have or what you call them. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") This guide assumes you have a Port account with admin access, and have completed the [onboarding process](https://docs.port.io/getting-started/overview). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") To support hierarchy tiers, we will extend the **Team** blueprint with a self-relation and a `type` property. Already have this set up? If your Team blueprint already has a `parent_team` self-relation and a `type` property, you can skip ahead to [Create dashboard](#create-dashboard) to further use the hierarchy in dashboards and widgets, or to [Next steps](#next-steps) to see what else you can build on top of it. ### Extend the Team blueprint[​](#extend-the-team-blueprint "Direct link to Extend the Team blueprint") The `Team` blueprint is available by default in Port. We need to extend it with a self-relation and a `type` property. Perform the following steps to extend the `Team` blueprint: **Add a self-relation** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Search for the `Team` blueprint using the search bar. 3. Click on `+ New relation` at the bottom of the blueprint card. 4. Fill out the form as seen below, then click `Create`: ![New relation form configured with Parent team as a single optional Team relation](/img/guides/hierarchyTiers/selfRelationForm.png) **Add a "type" property** 1. Click on `+ New property` in the `Team` blueprint card, and choose `Enum` as the property type. 2. Fill out the form with the options matching your hierarchy tiers, then click `Create`. This property is what distinguishes between hierarchy levels (e.g. "Group" vs "Team") and is also used for filtering in dashboard widgets. You can use the following example: ![Type enum property form with title, identifier, and string item type configured](/img/guides/hierarchyTiers/typePropertyForm1.png)![Type enum property options for Team and Group with Team as the default](/img/guides/hierarchyTiers/typePropertyForm2.png) ### Assign types & parent teams[​](#assign-types--parent-teams "Direct link to Assign types & parent teams") To finish setting up the data model, we need to assign each team a type and a parent team. 1. Click on the ![entitiesTable icon](/img/guides/icons/entitiesTable.svg)![entitiesTable icon](/img/guides/icons/dark/entitiesTable.svg) button in the `Team` blueprint card to view all of your team entities. 2. Assign a type and a parent team to each team by hovering over the relevant property and clicking on the ![EditProperty icon](/img/guides/icons/EditProperty.svg)![EditProperty icon](/img/guides/icons/dark/EditProperty.svg) button. * Set groups to type `group` and teams to type `team`. * Set each team's `parent_team` to its parent group. Table properties To make the process of assigning types and parent teams easier, you can use the ![ManageProperties icon](/img/guides/icons/ManageProperties.svg)![ManageProperties icon](/img/guides/icons/dark/ManageProperties.svg) `Manage properties` button in the top-right corner of the table to hide other columns. ## Create dashboard[​](#create-dashboard "Direct link to Create dashboard") Now that our data model is set up, let's create a dedicated dashboard for group leads. 1. Go to the [catalog](https://app.port.io/catalog) page in Port. 2. In the left sidebar, click on `+`, then select `New dashboard`. 3. Name the dashboard "Group Lead". Optionally, give it a description and select an icon, then click `Create`. Group membership requirement The filters rely on the logged-in user's team membership, so users must be assigned to a team at the level the dashboard is scoped to. For this guide's example, that means being a member of a **Group**-level team. Next, let's add two useful tables to the dashboard: ### Table 1: all of the group's teams[​](#table-1-all-of-the-groups-teams "Direct link to Table 1: all of the group's teams") The first table will display all of the teams under the group of the logged-in user. 1. Click on `+ Widget` in the top-right corner of the dashboard, then select `Table`. 2. Fill out the form as seen below: ![New table form configured for all teams in the current group](/img/guides/hierarchyTiers/allTeamsTable.png) 3. Define filters to ensure that only the relevant teams are displayed. Click on the `Filters` button. 4. Click on `Edit JSON`, and paste the following JSON. **Note: the filters in this table use contextual query rules, which can only be configured via JSON and not through the UI.** ``` { "combinator": "and", "rules": [ { "value": "Team", "property": "type", "operator": "=" }, { "operator": "matchAny", "property": { "path": [ { "relation": "parent_team", "maxHops": 1 } ] }, "value": { "context": "userTeams", "property": "$identifier" } } ] } ``` This JSON object defines two filters with an `AND` operator between them: * The first filter ensures that only teams (not groups) are displayed. * The second filter uses `matchAny` with [`maxHops`](/context-lake/consuming-the-lake/search-and-query/operators/relation-operators/.md#maxhops-limitation) to find teams that are one level below in the hierarchy via the `parent_team` self-relation. The value is resolved dynamically based on the logged-in user's team membership using a [contextual query rule](/context-lake/consuming-the-lake/search-and-query/operators/relation-operators/.md#self-relation). The value of `1` reflects the single level between groups and teams in this example - increase it to match the depth of your own hierarchy. 5. Click `Save` to save the filters, then click `Save` again to save the table. ### Table 2: all services owned by the group[​](#table-2-all-services-owned-by-the-group "Direct link to Table 2: all services owned by the group") 1. Click on `+ Widget` in the top-right corner of the dashboard, then select `Table`. 2. Fill out the form as seen below: ![New table form configured for services owned by the current group](/img/guides/hierarchyTiers/ownedServicesTable.png) 3. Define filters to ensure that only the relevant services are displayed. Click on the `Filters` button. 4. Click on `Edit JSON`, and paste the following JSON. **Note: the filters in this table use contextual query rules, which can only be configured via JSON and not through the UI.** ``` { "combinator": "or", "rules": [ { "value": { "context": "userTeams", "property": "$identifier" }, "property": "$team", "operator": "containsAny" }, { "operator": "matchAny", "property": { "path": [ "$team", { "relation": "parent_team", "maxHops": 1 } ] }, "value": { "context": "userTeams", "property": "$identifier" } } ] } ``` This JSON object defines two filters with an `OR` operator between them: * The first filter matches services directly owned by one of the logged-in user's teams. * The second filter uses `matchAny` with [`maxHops`](/context-lake/consuming-the-lake/search-and-query/operators/relation-operators/.md#maxhops-limitation) to find services whose owning team is one level below in the hierarchy via the `parent_team` self-relation. The value is resolved dynamically based on the logged-in user's team membership using a [contextual query rule](/context-lake/consuming-the-lake/search-and-query/operators/relation-operators/.md#self-relation). * The value of `1` reflects the single level between groups and teams in this example - increase it to match the depth of your own hierarchy. * Avoid using the `relatedTo` operator for this - it matches all paths between blueprints, not just the hierarchy path, which leads to unintended results and poor performance. 5. Click `Save` to save the filters, then click `Save` again to save the table. Dashboard-level filters If you want to apply a filter across all widgets displaying the same blueprint, you can define it once at the [dashboard level](/interface-builder/port-interface/page/dashboard-page/.md#dashboard-filters) instead of per widget. ## Advanced filter patterns[​](#advanced-filter-patterns "Direct link to Advanced filter patterns") The examples above use a simple hierarchy with services as the primary entity. This section covers how to adapt the same approach for more complex scenarios, different entity types, and visibility in both directions across the hierarchy. ### Executive visibility[​](#executive-visibility "Direct link to Executive visibility") A CTO wants one dashboard with all services, incidents, and metrics across the entire org, without being manually added to every team downstream and re-added whenever teams change. To achieve that, assign the executive as a member of the **top-level team** in your hierarchy, then use the same filter as above with `maxHops` set to the full depth of your hierarchy: **Filter: all services across the org (click to expand)** ``` { "combinator": "or", "rules": [ { "value": { "context": "userTeams", "property": "$identifier" }, "property": "$team", "operator": "containsAny" }, { "operator": "matchAny", "property": { "path": [ "$team", { "relation": "parent_team", "maxHops": 10 } ] }, "value": { "context": "userTeams", "property": "$identifier" } } ] } ``` * The first rule matches services directly owned by the executive's team. * The second rule traverses up to 10 levels of `parent_team` relations, capturing services owned by any team in the hierarchy below. As long as your hierarchy does not exceed 10 levels, the dashboard will update automatically whenever teams are added or reorganized. ### Choosing the right filter property[​](#choosing-the-right-filter-property "Direct link to Choosing the right filter property") The `path` in your filter defines how Port traverses relations to reach the entity's owning team. The right path depends on how each entity type connects to teams. **Filter by built-in `$team` relation** Some entities, such as services, relate to a team through the built-in `$team` property. In that case, use `$team` as the starting point in the path, as shown in the examples above. This is the most common case. **Filter by custom direct team relation** Some entities have a custom relation to a team. For example, an incident blueprint may have a `supporting_team` relation. Since `$team` does not exist on incidents, use the relation's identifier directly in the path: **Filter: incidents by custom team relation (click to expand)** ``` { "combinator": "and", "rules": [ { "operator": "matchAny", "property": { "path": [ "supporting_team", { "relation": "parent_team", "maxHops": 10 } ] }, "value": { "context": "userTeams", "property": "$identifier" } } ] } ``` **Filter by user-to-team traversal** Some entities relate to a team indirectly through a user. For example, a pull request has an `author` relation to a User, and that user belongs to teams. To show a manager all pull requests by developers in their group, traverse from the entity through the user to the team: **Filter: pull requests by user-to-team traversal (click to expand)** ``` { "combinator": "and", "rules": [ { "operator": "matchAny", "property": { "path": [ "author", "$team", { "relation": "parent_team", "maxHops": 10 } ] }, "value": { "context": "userTeams", "property": "$identifier" } } ] } ``` **Filter by mirror property (indirect relation)** If an entity relates to a team only through an intermediate entity, you cannot use `matchAny` to traverse that path. For example, a ServiceNow incident has no direct relation to a team - it relates to a ServiceNow service, which then relates to a team. In that case, `matchAny` path traversal will not work. The solution is to add a [mirror property](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md) on the incident blueprint that reflects the owning team from the service (e.g. `service_owner` mirroring `service_now_service.$team`). Once the mirror property exists, you can filter directly against it using `containsAny`: **Filter: incidents by mirror property (click to expand)** ``` { "combinator": "and", "rules": [ { "value": { "context": "userTeams", "property": "$identifier" }, "property": "service_owner", "operator": "containsAny" } ] } ``` This works because the mirror property surfaces the owning team's identifier directly on the incident, making it filterable without needing to traverse relations. **Filter by team lead relation** Some organizations track leadership via a dedicated relation on the Team blueprint (e.g. `team_lead`) rather than through team membership. If yours does, you can scope dashboards to whoever is set as the lead of a team. The filter traverses from the service up through its owning team, up the hierarchy, and then to the `team_lead` relation, matching against the current user: **Filter: services by team lead (click to expand)** ``` { "combinator": "and", "rules": [ { "operator": "matchAny", "property": { "path": [ "$team", { "relation": "parent_team", "maxHops": 10 }, "team_lead" ] }, "value": { "context": "user", "property": "$identifier" } } ] } ``` ## Next steps[​](#next-steps "Direct link to Next steps") Once your hierarchy is set up, you can: * Create similar dashboards for managers of each tier in your hierarchy, where they can track and visualize useful information about the teams they are responsible for. * Use the [View as](/platform-administration/users-and-teams/view-as-different-user/.md) feature to verify the filters work correctly from each user's perspective. * Add additional widgets to the dashboard, such as: * A pie chart showing the distribution of incidents across teams in your group. * Number charts displaying the number of resolved issues per team in a given time period. * A chart/table displaying the members of each team in your group, by role/name. --- # Business context Business context enriches your software catalog with the organizational, financial, and operational signals that help prioritize work, assess risk, and align engineering decisions with business objectives. It complements technical metadata by answering: *"What matters most to the business?"* When AI agents and workflows understand this context, they can prioritize vulnerabilities by revenue impact, route incidents to the right owner, and enforce policies automatically — all without human triage. ## What business context includes[​](#what-business-context-includes "Direct link to What business context includes") ### Cost and financial context[​](#cost-and-financial-context "Direct link to Cost and financial context") * **Cost center attribution** - track which department or budget owns each resource via [AWS Cost integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/.md) or [Kubecost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/.md). * **Revenue impact** - tag services that directly generate revenue or support revenue-generating features. * **Cloud spending patterns** - understand resource costs to inform optimization and prioritization decisions. ### Criticality and risk context[​](#criticality-and-risk-context "Direct link to Criticality and risk context") * **Business criticality levels** - classify services (e.g. mission-critical, customer-facing, internal tooling) to drive different [SLA requirements](/guides/all/track-slos-and-slis-for-services/.md), triage workflows, and automation policies. * **Disaster recovery tier** - define RTO/RPO requirements based on business impact to inform backup strategies and incident response priorities. * **Data sensitivity** - mark resources handling PII, financial data, or regulated information to enforce compliance controls. * **Compliance scope** - tag services subject to SOC 2, GDPR, HIPAA, or PCI-DSS to ensure audit readiness. ### Operational context[​](#operational-context "Direct link to Operational context") * **SLAs and SLOs** - define service-level agreements and objectives to measure reliability, track [MTTR](/guides/all/track-and-show-mtbf-for-services/.md), and ensure SLA compliance. * **On-call ownership** - integrate [PagerDuty schedules](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/.md) to understand who is responsible right now for incident response. * **Escalation policies** - define who to notify and when for different severity levels based on business impact. ### Organizational context[​](#organizational-context "Direct link to Organizational context") * **Team affiliation** - connect services to teams via [GitHub CODEOWNERS](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) or [Jira project mappings](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/examples/.md) for clear ownership. * **Reporting hierarchy** - map organizational structure (team → department → division) for escalation paths. * **Business unit alignment** - associate services with product lines or business units to understand impact radius. ### Customer and product context[​](#customer-and-product-context "Direct link to Customer and product context") * **Customer tier** - identify which customer segments are affected (e.g. enterprise, gold-tier, freemium) to prioritize incidents and features affecting high-value customers. * **Product lifecycle stage** - tag services by maturity (closed beta, open beta, GA, deprecated) to set appropriate expectations and SLAs. ## Why business context matters[​](#why-business-context-matters "Direct link to Why business context matters") When AI agents and workflows have access to business context, they can: * **Prioritize vulnerabilities** affecting revenue-generating production services over internal dev tools. * **Route incidents** to the right on-call engineer based on service ownership and escalation policies. * **Estimate blast radius** of a deployment by understanding dependent services and their business criticality. * **Automatically enforce policies** like "critical services must have SLOs defined" or "PII-handling services require SOC 2 compliance checks". * **Calculate risk scores** that combine technical severity (CVSS) with business impact (criticality, revenue, SLA, customer tier). * **Adjust incident response** based on affected customer tier - a P1 incident affecting enterprise customers triggers immediate executive notification, while the same issue in closed beta may follow standard on-call procedures. ## Examples[​](#examples "Direct link to Examples") * Risk-based vulnerability triage * Context-aware incident routing * Disaster recovery policy enforcement **Scenario:** A critical CVE is discovered in a library used by multiple services. **Without business context:** The security team gets hundreds of alerts with no clear way to prioritize which services to patch first. **With business context in Port:** 1. Port enriches each vulnerability with: * Service **business criticality** (mission-critical vs. internal). * **Revenue impact** (directly revenue-generating or not). * **SLA requirements** (99.99% uptime vs. best-effort). * **Data sensitivity** (handles customer PII or not). * **Compliance scope** (subject to SOC 2 audit). * **Customer tier** (enterprise vs. freemium). 2. An AI agent or automation calculates a **risk score**: ``` Risk = CVE Severity × (Business Criticality + Revenue Impact + SLA Weight + Compliance Factor + Customer Tier) ``` 3. Results in a prioritized triage queue: * **Fix immediately**: Payment service (mission-critical, revenue-generating, 99.99% SLA, PCI-DSS scope, enterprise customers). * **Fix this sprint**: Customer portal (customer-facing, revenue-supporting, 99.5% SLA, gold-tier customers). * **Backlog**: Internal dev tools (low criticality, no SLA, internal users only). **Scenario:** A production API starts returning 500 errors during business hours. **Without business context:** A generic "production down" alert leads to manual escalation and wasted time finding the right owner. **With business context in Port:** 1. Port's incident automation queries the context lake for: * Service **on-call schedule** from PagerDuty. * Service **business criticality** and **SLA** (99.99% uptime). * **Revenue impact** (payment processing = revenue-blocking). * **Customer tier affected** (enterprise customers experiencing errors). * **Product lifecycle stage** (GA with 10,000+ active users). * **Recent deployments** (was there a recent change?). 2. Automation takes action based on context: * **Closed beta, freemium users**: page on-call engineer via standard PagerDuty workflow. * **GA, enterprise customers**: immediate P1 escalation → page incident captain, notify VP Engineering, create executive war room. 3. Result: faster MTTR and an appropriate response because both responders and escalation paths match business impact. *Learn more:* [Deploy incident updates with n8n](/guides/all/ai-powered-incident-management/.md#generate-incident-updates-with-ai). **Scenario:** A platform team needs to ensure all mission-critical services have disaster recovery plans. **Without business context:** Manual spreadsheet tracking leads to inconsistent enforcement and audit findings. **With business context in Port:** 1. Services are tagged with: * **Business criticality** (mission-critical, high, medium, low). * **Disaster recovery tier** (Tier 1: RTO < 1hr, Tier 2: RTO < 4hr, Tier 3: RTO < 24hr). * **Customer tier impact** (affects enterprise vs. freemium). * **Product lifecycle** (GA services have stricter DR requirements than beta). 2. A Port automation enforcement policy: ``` IF service.businessCriticality == "mission-critical" AND service.productLifecycle == "GA" AND service.disasterRecoveryTier != "Tier 1" THEN fail_scorecard("Missing DR Plan") AND create_jira_ticket(owner, "Define DR plan with RTO < 1hr for GA mission-critical service") ``` 3. A scorecard dashboard shows: * 12/15 mission-critical GA services have Tier 1 DR plans. * 3 services out of compliance → auto-created tickets assigned to owners. * Beta services excluded from strict DR requirements until GA launch. ## How to ingest business context[​](#how-to-ingest-business-context "Direct link to How to ingest business context") Business context comes from many sources: * **Cloud providers** - cost data via [AWS Cost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/.md) and [Kubecost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/.md). * **Incident management** - on-call schedules via [PagerDuty](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/.md) and [ServiceNow](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/servicenow/.md). * **Source control** - team ownership via [GitHub CODEOWNERS](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) and [GitLab](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md). * **Project management** - business unit and stakeholders via [Jira](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/examples/.md). * **Manual enrichment** - business criticality, revenue impact, SLAs, customer tier, and product lifecycle added as blueprint properties and updated via [self-service actions](/workflows/actions-and-automations/setup-backend/github-workflow/.md) or [API](/api-reference/port-api/). ## How to model business context in Port[​](#how-to-model-business-context-in-port "Direct link to How to model business context in Port") Some of the most valuable catalog context is knowledge that already exists in your organization but is scattered across teams and tools. The following patterns show how to capture that context in Port so humans, workflows, and agents can use it consistently. ### Set up ownership[​](#set-up-ownership "Direct link to Set up ownership") Ownership answers "who is responsible for this service right now." It enables routing, escalation, approvals, and accountability across the SDLC. If you use Port's default components, start with the default ownership model and extend it to match your org. To go deeper, see [Ownership in Port](/context-lake/business-context/ownership/.md) and [Default blueprints](/context-lake/data-model/setup-blueprint/default-blueprints/.md). ### Set up service criticality and tiers[​](#set-up-service-criticality-and-tiers "Direct link to Set up service criticality and tiers") Criticality and tiering help you prioritize engineering work and apply different policies by service importance - for example, incident response expectations, approval requirements, or compliance checks. Model criticality as a property on the entities you care about (for example, services) and use scorecards and workflows to enforce stricter standards for high-criticality services. ### Set up team assignments[​](#set-up-team-assignments "Direct link to Set up team assignments") Team assignments capture how services, repositories, and infrastructure map to teams. This supports routing, dashboards, and agent workflows that need to know which team owns an asset. To go deeper, see [Manage users and teams](/platform-administration/users-and-teams/manage-users-teams/.md) and [Ownership in Port](/context-lake/business-context/ownership/.md). ### Set up tags and labels[​](#set-up-tags-and-labels "Direct link to Set up tags and labels") Tags and labels let you slice your catalog consistently - for example, by domain, environment, customer impact, or compliance scope. Use blueprint properties to model the tags you care about, then use [search and query](/context-lake/consuming-the-lake/search-and-query/overview/.md) to filter entities by those tags. ### Add custom metadata[​](#add-custom-metadata "Direct link to Add custom metadata") Custom metadata is where you capture organization-specific signals that are not available from integrations - for example, support tier, internal readiness gates, or ownership exceptions. To go deeper, see [Set up blueprints](/context-lake/data-model/setup-blueprint/overview/.md). --- # Manage ownership in Port Ownership in Port defines who is responsible for specific entities in your internal developer portal — such as services, repositories, or incidents. Managing ownership correctly ensures clear accountability, smoother collaboration, and accurate reporting.
Ownership in Port is represented through relationships between **users**, **teams**, and **catalog entities**. This page outlines how to manage ownership in Port, from syncing users and teams to assigning them to catalog entities, and visualizing the data. ## How does it work?[​](#how-does-it-work "Direct link to How does it work?") When creating a Port account, some default blueprints are created for you. Two of them are the `User` and `Team` blueprints, which are used to represent your users and teams. Using these blueprints, we can define ownership of resources in the software catalog to specific users or teams. Defining ownership in Port is composed of several steps: 1. [Sync Users](#sync-users) - the first step in managing ownership is syncing your users from 3rd party tools into Port, and connecting to the relevant Port user. This allows you to have a single component in your portal that represents your user across your entire ecosystem. 2. [Sync Teams](#sync-teams) - just like users, teams from 3rd party tools can be synced into Port, and connected to the relevant Port teams. 3. [Assign Users to Teams](#assign-users-to-teams) - once Port users and teams are synced and connected to each other, users can get visibility into the resources owned by their team/s. 4. [Assign Teams to Catalog Entities](#assign-teams-to-catalog-entities) - define ownership of resources in your catalog to Port teams.
For example, a GitHub repository can be owned by the `frontend` team. 5. [Assign Users to Catalog Entities](#assign-users-to-catalog-entities) - define ownership of resources in your catalog to Port users.
For example, a PagerDuty incident can be owned by the current `on-call` user. ## How ownership is defined in your catalog[​](#how-ownership-is-defined-in-your-catalog "Direct link to How ownership is defined in your catalog") Under the hood, Port represents ownership using two mechanisms on a blueprint: the `ownership` property, and the `$team` meta-property that is available on every entity. ### The `team` meta-property[​](#the-team-meta-property "Direct link to the-team-meta-property") The value returned by the `$team` meta-property depends on the [ownership type](#the-ownership-property) defined for the blueprint - it will be empty for blueprints with no ownership, directly set for blueprints with Direct ownership, or inherited for blueprints with Inherited ownership. Each entity has a [meta-property](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md) named `team`, that stores which teams own the entity.
As an `admin`, you can also set blueprint permissions according to this field. The `team` meta-property is an array of the owning teams' `identifiers`.
Here is an example entity: ``` { "identifier": "pod-1", "title": "Awesome pod", "team": ["some-team-identifier", "another-team-identifier"], "blueprint": "Pod", "properties": { ... }, "relations": {} } ``` #### Reference an entity's team[​](#reference-an-entitys-team "Direct link to Reference an entity's team") In places where you need to reference an entity's team, use `$team` to reference the meta-property.
For example, in a search query: ``` { "operator": "containsAny", "property": "$team", "value": ["team-identifier"] } ``` ### The `ownership` property[​](#the-ownership-property "Direct link to the-ownership-property") All blueprints have an `ownership` property that tells Port how to set team ownership for its entities.
This field is not mandatory. Terraform support The `ownership` property is also supported in Terraform starting from version **2.1.4**. These are the available options for the `ownership` property: 1. **No ownership** The blueprint has no defined ownership.
The `$team` property will have no meaningful value. ``` { "identifier": "packageVersion", "title": "Package Version", ... } ``` 2. **Direct** Ownership of the blueprint's entities will be defined by a hidden [relation]() to the `Team` blueprint.
The `Owning teams` column will be visible in tables containing entities of the blueprint.
Direct ownership supports [multi source ingestion](#multi-source-ingestion-for-owning-teams), so multiple sources can each own a slice of the owning teams. ``` { "identifier": "packageVersion", "title": "Package Version", "ownership": { "type": "Direct" } ... } ``` 3. **Inherited** Ownership of the blueprint's entities will be inherited from a different related blueprint with `Direct` ownership.
The `Owning teams` column will be visible in tables containing entities of the blueprint, but will not be editable. The `path` key is a dot-separated path of **relation identifiers** that lead to the desired blueprint. ``` { "identifier": "packageVersion", "title": "Package Version", "ownership": { "type": "Inherited", "path": "relationIdentifier1.relationIdentifier2.relationIdentifier3" } ... } ``` You can also change the `title` of the ownership property. The default value is `Owning teams`. ``` { "identifier": "packageVersion", "title": "Package Version", "ownership": { "type": "Direct", "title": "My Custom Ownership Title" } ... } ``` ### Multi source ingestion for owning teams[​](#multi-source-ingestion-for-owning-teams "Direct link to Multi source ingestion for owning teams") By default, setting an entity's `team` replaces the full list of owning teams. Enable **Multi source ingestion** on a blueprint with `Direct` ownership to let multiple [connectors](/context-lake/ingestion/overview/.md) or API callers each own a slice of the owning teams without overwriting each other. In the [Data model](https://app.port.io/settings/data-model) page, turn on **Multi source ingestion** when setting the blueprint's ownership to `Direct`. In the API, set `"union": true` on the ownership property. Cannot be changed later This setting cannot be changed after ownership is set to `Direct`. ``` { "identifier": "packageVersion", "title": "Package Version", "ownership": { "type": "Direct", "union": true } ... } ``` #### Example[​](#example "Direct link to Example") The `service` blueprint has multi source ingestion enabled on its ownership. For an entity of that blueprint: 1. **Monday** - the GitHub integration writes `{ "github": ["frontend-team"] }`. 2. **Tuesday** - the PagerDuty integration writes `{ "pagerduty": ["oncall-team"] }` without removing GitHub's team. 3. **Wednesday** - Alice edits the entity in the UI and adds `platform-team`. Port saves her changes under `user:alice`. A **GET** returns the merged, deduplicated `$team` meta-property: ``` { "identifier": "checkout-service", "team": ["frontend-team", "oncall-team", "platform-team"] } ``` Each writer only updates its own source key. #### Write and read values[​](#write-and-read-values "Direct link to Write and read values") **Write** (`POST`, `PATCH`, `PUT`) - send one source key per update, mapped to an array of team identifiers: ``` { "team": { "github": ["frontend-team"] } } ``` Use `null` instead of an array to clear a source's slice. Source keys must match `^[A-Za-z0-9._:@/-]{1,128}$`. **Read** (`GET`, search) - Port returns the merged, deduplicated `$team` meta-property, same as regular ownership. In the UI, you edit the assembled list of owning teams directly. Port stores UI edits under `user:{userId}` automatically. Consistent with relations and array properties Multi source ingestion works the same way for [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md#multi-source-ingestion) and [array properties](/context-lake/data-model/setup-blueprint/properties/array/.md#multi-source-ingestion): each writer owns a source key, and reads return the deduplicated combination of all sources. #### Constraints[​](#constraints "Direct link to Constraints") * Only available on blueprints with `Direct` ownership. * Cannot be changed after ownership is set to `Direct`. Create a new ownership configuration and migrate the values instead. * Sending a plain array, multiple source keys, or an invalid source key returns `422`. ## Sync users[​](#sync-users "Direct link to Sync users") Users can be synced into Port either manually or automatically, depending on your integrations. **Note** that when using an SSO provider, users and teams are synced automatically. **Automatically**: * **Built-in integrations**: Update the integration mapping to connect the Port user to the integration user: * GitHub * GitLab * Azure DevOps * Jira ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: .login title: .login blueprint: '"_user"' relations: git_hub_user: .login ``` ``` - kind: group-with-members selector: query: 'true' includeBotMembers: 'true' includeInheritedMembers: 'true' port: itemsToParse: .__members entity: mappings: identifier: .item.username title: .item.name blueprint: '"_user"' relations: gitlab_user: .item.username ``` ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: '.id' title: '.user.displayName' blueprint: '"_user"' relations: azure_devops_user: '.id' ``` ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: .accountId title: .displayName blueprint: '"_user"' relations: jira_user: .accountId ``` * **Custom integrations**: We can create a simple automation to link new Port users to the matching integration user upon creation: * [Slack](https://docs.port.io/guides/all/map-external-users-and-teams-to-port-accounts?integration=slack#slack-sync-slack-users-when-a-new-port-user-is-added) * [ServiceNow](https://docs.port.io/guides/all/map-external-users-and-teams-to-port-accounts?integration=servicenow#servicenow-sync-servicenow-users-when-a-new-port-user-is-added) * [HiBob](https://docs.port.io/guides/all/map-external-users-and-teams-to-port-accounts?integration=hibob#hibob-sync-hibob-users-when-a-new-port-user-is-added) **Manually**: * [**Self-service Action (SSA)**]() – [Register your user](https://app.port.io/self-serve?action=_onboard_your_user) An out-of-the-box self service action used to register the logged-in user in Port, connecting it to the relevant 3rd party user/s. * **Register a new user**
This can be done from the [Users catalog page](https://app.port.io/_users):
Click on the `+ User` button, then click on `Register existing user`. This is useful for inviting a new user and defining their relations to 3rd party users in a single step. * **Edit an existing user entity**
This can also be done from the [Users catalog page](https://app.port.io/_users):
Click on the `...` button, then click on `Edit`. Ideal when updating a user with new data — for example, connecting it to a Slack user. **Custom integrations**:
3rd party tools for which Port does not have a built-in integration can be synced manually using the following guides: * [Slack](https://docs.port.io/guides/all/map-external-users-and-teams-to-port-accounts?integration=slack) * [ServiceNow](https://docs.port.io/guides/all/map-external-users-and-teams-to-port-accounts?integration=servicenow) * [HiBob](https://docs.port.io/guides/all/map-external-users-and-teams-to-port-accounts?integration=hibob) ## Sync teams[​](#sync-teams "Direct link to Sync teams") Teams can also be synced into Port either manually or automatically, depending on your integrations and conventions. **Note** that when using an SSO provider, teams are synced automatically. **Automatically**: * **Built-in integrations**: Using the integration mapping, we can define how to create/update teams in Port. For example, the following mapping can be used to create/update teams using GitHub: ``` - kind: team selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"_team"' relations: git_hub_team: .id | tostring ``` In this example, if the team already exists in Port, it will be connected to the GitHub team with the same identifier.
If it does not exist, it will be created and connected to the GitHub team with the same identifier. **Manually**: * **Register a new team**
This can be done from the [Teams catalog page](https://app.port.io/_teams):
Click on the `+ Team` button. This is useful for creating a new team and defining its relations to 3rd party teams in a single step. * **Edit an existing team entity**
This can also be done from the [Teams catalog page](https://app.port.io/_teams):
Click on the `...` button, then click on `Edit`. Ideal when updating a team with new data — for example, connecting it to a Sentry team. ## Assign users to teams[​](#assign-users-to-teams "Direct link to Assign users to teams") In many cases, ownership is assigned to a team and not a specific user. By default, Port allows you to assign one or more owning teams to each entity in your catalog. As a user, it's important to see all of the resources owned by you or your team/s. Therefore, the next step is to ensure that all Port users are assigned to the relevant team/s. **Automatically**: * When using SSO, users and teams are created and connected automatically. * When using Entra ID, you can use this [integration tool](https://github.com/port-experimental/entra-id-provisioner) to sync users and teams into Port. * When using Gitlab or ADO integrations, you can connect Port team to Port user when fetching the integration's teams. For example, the following mapping connects a team in Port to a user in Port based on the GitLab association (assuming the GitLab group is already mapped to the Port team): ``` - kind: group-with-members selector: query: 'true' includeBotMembers: 'true' includeInheritedMembers: 'true' port: itemsToParse: .__members entity: mappings: identifier: .item.email title: .item.name team: .full_path blueprint: '"_user"' ``` **Manually**: * [**Self-service action (SSA)**]() – [Add team members](https://app.port.io/self-serve?action=_onboard_existing_team)
An out-of-the-box self service action used to add users to an existing team. * **Edit a user entity**
This can be done from the [Users catalog page](https://app.port.io/_users):
Click on the `...` button, then click on `Edit`. * When creating/inviting a new user from the UI, you can assign them to teams as part of the creation process. ## Assign teams to catalog entities[​](#assign-teams-to-catalog-entities "Direct link to Assign teams to catalog entities") **Automatically**: * If using GitHub, you can use the [team-mapper](https://github.com/port-experimental/repo-team-mapper) script to map repositories to teams based on commit history. * If an entity has an property or relation that contains the integration team identifier (e.g., GitHub repository → `github-teams`), update the mapping so that the `github-teams` value will automatically be set in `owning teams`. ``` - kind: repository selector: query: 'true' teams: true port: entity: mappings: identifier: .full_name title: .name blueprint: '"githubRepository"' properties: readme: file://README.md url: .html_url defaultBranch: .default_branch $team: '[.teams[].id | tostring]' relations: githubTeams: '[.teams[].id | tostring]' ``` Note that this assumes Port team identifiers match GitHub team identifiers. **Manually**: * [**Self-service action (SSA)**]() – [Own services](https://app.port.io/self-serve?action=set_ownership)
An out-of-the-box self service action used to assign ownership of one or more services to a specific team. * **Edit an entity**
This can be done from the entity's relevant catalog page:
Click on the `...` button, then click on `Edit`, and change the `Owning teams` field. * When creating a new entity, assign its owning team(s) as part of the creation process. ## Assign users to catalog entities[​](#assign-users-to-catalog-entities "Direct link to Assign users to catalog entities") In some cases, ownership needs to be assigned to a specific user and not a team. For example, a PagerDuty incident may be owned by the current on-call user, or a GitHub Pull Request may be owned by the creator. **Automatically**: In many Port integrations, blueprints can have out-of-the-box relations to the **3rd party user blueprint**, with a default mapping configuration that automatically sets the value of this relation. Additionally, some blueprints have out-of-the-box relations to the **Port user blueprint**, with a default mapping configuration that automatically sets the value of this relation as well. Here are some common examples: * The `GitHub Pull Request` blueprint has a default relation ("git\_hub\_creator") to the **GitHub user** that created the PR.
Additionally, it has a default relation ("creator") to the **Port user** associated with the GitHub user that created the PR. * The `Jira Issue` blueprint has a default relation ("reporter") to the **Port user** that reported the issue. * The `PagerDuty Incident` blueprint has a default relation ("assignee") to the **Port user** that is assigned to the incident. **Manually**: * **Edit an entity**
If a blueprint has a relation to the Port user blueprint, you can edit the entity from its catalog page and change the value of this relation:
Click on the `...` button, then click on `Edit`, and change the relation value. ## Visualize user & team data[​](#visualize-user--team-data "Direct link to Visualize user & team data") Now that we have set up ownership in our catalog, let's see how we can visualize it in useful ways. ### Default pages & filters[​](#default-pages--filters "Direct link to Default pages & filters") * By default, the following pages are available in your catalog: * **User entity page**: View all Port users and their related 3rd-party users and the Port teams they belong to. * **Team entity page**: View all Port teams and their related 3rd-party teams. * In any table or chart, you can use the `My Teams` or `My` filters to show only entities that belong to your teams or are owned by you. ### User management dashboard[​](#user-management-dashboard "Direct link to User management dashboard") Now let's create a dedicated dashboard to manage users and ownership of entities in your catalog.
This dashboard will allow you to: * View which users are assigned to which team/s. * View which users from 3rd-party tools are connected to which Port user/s. * View which teams from 3rd-party tools are connected to which Port team/s. * View which services are owned by which team/s. * Execute several [**self-service actions**]() to onboard users to teams, register new users, and assign ownership of services to teams. ### Create a dashboard[​](#create-a-dashboard "Direct link to Create a dashboard") 1. Navigate to your [software catalog](https://app.port.io/organization/catalog). 2. Click on the `+` button in the left sidebar. 3. Select **New dashboard**. 4. Name the dashboard **Ownership Management**. 5. Input `Manage and onboard your users and teams` under **Description**. 6. Select the `Users` icon. 7. Click `Create`. We now have a blank dashboard where we can start adding widgets to visualize ownership insights. #### Add widgets[​](#add-widgets "Direct link to Add widgets") In the new dashboard, create the following widgets: **Users assignment (click to expand)** 1. Click `+ Widget` and select **Table**. 2. Title: `Users assignment`. 3. Choose **User** as the **Blueprint**. 4. Click `Save`. 5. Click on the **`...`** button in the top right corner of the table and select **Customize table**. 6. Define the visible columns to include the user title, team, and third parties associated user. 7. Click on the **save icon** in the top right corner of the widget to save the customized table. ![Users assignment table with titles and teams](/img/software-catalog/pages/usersAssignmentWidget.png) ***
**Users with no teams (click to expand)** 1. Click **`+ Widget`** and select **Pie chart**. 2. Title: `Users with no teams`. 3. Choose the **User** blueprint. 4. Under `Breakdown by property`, select the **Ownership - Has Team** property 5. Click **Save**. ![Pie chart of users with no teams](/img/software-catalog/pages/usersWithNoTeamsWidget.png) ***
**Teams assignment (click to expand)** 1. Click **`+ Widget`** and select **Table**. 2. Title: `Teams assignment`. 3. Choose the **Team** blueprint. 4. Click **Save**. 5. Click on the **`...`** button in the top right corner of the table and select **Customize table**. 6. Define the visible columns to include the team's title, and third parties associated teams. 7. Click on the **save icon** in the top right corner of the widget to save the customized table. ![Teams assignment table with Datadog and GitHub columns](/img/software-catalog/pages/teamsAssignmentWidget.png) ***
**Services ownership (click to expand)** 1. Click **`+ Widget`** and select **Table**. 2. Title the widget **Services ownership**. 3. Choose the **Service** blueprint 4. Click **Save** to add the widget to the dashboard. 5. Click on the **`...`** button in the top right corner of the table and select **Customize table**. 6. Define the visible columns to include the service title and the owning teams. 7. Click on the **save icon** in the top right corner of the widget to save the customized table. ![Services ownership table with owning teams](/img/software-catalog/pages/servicesOwnershipWidget.png) ***
**Ownership management actions (click to expand)** 1. Click **`+ Widget`** and select **Action Card**. 2. Under `Actions`, select all the Ownership management actions we created: * **Add team members** * **Register your user** * **Own services** 3. Title: `Ownership management`. 4. Click **Save**. ![Ownership management self-service action card](/img/software-catalog/pages/ownershipSSAWidget.png) --- # Examples In this section we'll show you a few examples of ways to use catalog permissions in your organization, and how to apply them. ## Use cases[​](#use-cases "Direct link to Use cases") The following configurations, among others, are available when using catalog permissions management: 1. Entities can be made immutable/partially immutable (can only read/create/delete/modify) for specific users/roles. For example: 1. `Deployment` entities are immutable for all roles, and `Cluster` entities are editable only by the blueprint **Moderators**. 2. **Members** can create a new `Microservice` entity, but are not permitted to delete a `Microservice` entity. 2. Each entity property/relation can be **readable** or **immutable** separately for specific users/roles. For example, the `repository_link` property can be immutable for all roles (except **Admin**). 3. Allow specific users/roles to **read** or **modify** only entities [owned by their team](#setting-permissions-by-team-ownership). For example, **Members** can edit only `Microservices` that belong to their team. 4. Action execution permissions can be given to specific users or roles. For example, you can allow every **Member** to create a new `Deployment` entity, however only `Deployment` **Moderators** can perform a day-2 action of "adding resources". ## Setting blueprint permissions[​](#setting-blueprint-permissions "Direct link to Setting blueprint permissions") To set permissions for a blueprint, click on the permissions button of the desired blueprint on the Data model page. This will open a modal that contains the permissions JSON and allows you to control every operation that can be performed on the blueprint or its entities. ### Role examples[​](#role-examples "Direct link to Role examples") * Let member register entity * Only let admin update property If you want to enable **Members** to register entities of a blueprint, you can change the JSON as follows: ``` { "entities": { "register": { "roles": ["Admin", "Env-moderator", "Member"], // changed from ["Admin", "Env-moderator"] "users": [], "teams": [], "ownedByTeam": false } } } ``` To allow only **Admins** to change the property `slackChannelUrl`, remove the Moderator role: ``` { "entities": { "updateProperties": { "slackChannelUrl": { "roles": ["Admin"], // changed from ["Admin", "Env-moderator"] "users": [], "teams": [], "ownedByTeam": false } } } } ``` ### User examples[​](#user-examples "Direct link to User examples") * Let user edit relation To grant permissions for a specific user to edit the `deployedAt` relation, add them to the users array: ``` { "entities": { "updateRelations": { "deployedAt": { "roles": ["Admin", "Env-moderator"], "users": ["some-user@myorg.com"], // changed from [] "teams": [], "ownedByTeam": false } } } } ``` ### Team examples[​](#team-examples "Direct link to Team examples") * Let team edit relation To grant permissions for a specific team to edit the `deployedAt` relation, add them to the teams array: ``` { "entities": { "updateRelations": { "deployedAt": { "roles": ["Admin", "Env-moderator"], "users": [], "teams": ["serviceMaintainers"], "ownedByTeam": false } } } } ``` ### Team ownership examples[​](#team-ownership-examples "Direct link to Team ownership examples") Some operations have the `ownedByTeam` flag. This allows you to set permissions by team ownership, rather than by roles or direct assignment to users. For example, the following JSON will allow **every user**, regardless of their role, to perform the action `delete_env` on `Env` entities that belong to a team they are a part of (entities that have the `team` property set): ``` { "actions": { "delete_env": { "execute": { "roles": ["Admin", "Env-moderator"], "users": [], "teams": [], "ownedByTeam": true // changed from false } } } } ``` ### Inherited ownership[​](#inherited-ownership "Direct link to Inherited ownership") Inherited ownership allows you to utilize relations to automatically assign and manage entity ownership. By using inherited ownership, you can configure entities to automatically inherit their ownership from entities they are related to. Inherited ownership can be configured by setting the `ownership` property in the [blueprint structure](/context-lake/data-model/setup-blueprint/relate-blueprints/.md#structure-table) with `"type": "Inherited"`.
The `path` key represents the relation path to the blueprint whose entity's teams we want to inherit. * The `path` key is a dot-separated path of **relation identifiers** that lead to the desired blueprint. * The target blueprint must have `Direct` ownership configured. * Inherited ownership can only be configured using a path of [`single`](/context-lake/data-model/setup-blueprint/relate-blueprints/.md#single-relation-structure) type relations. For example, the following JSON (added to the **blueprint definition**) will configure the `myBlueprint` blueprint's entities to inherit their ownership from the `myExtraRelatedBlueprint` blueprint's entities: ``` { "identifier": "myBlueprint", // ...blueprint properties "ownership": { "type": "Inherited", "path": "myRelatedBlueprint.myExtraRelatedBlueprint" } } ``` In the example above, the relation chain is: `myBlueprint` -> `myRelatedBlueprint` -> `myExtraRelatedBlueprint`. The `Owning teams` column will be visible in tables containing entities of the blueprint, but will not be editable. Conflict with direct team relations Defining both `ownership.type: "Inherited"` and a direct relation to the Team blueprint (e.g., `teams`) in the same blueprint will cause the relation to be removed and ownership to fall back to `"Direct"`. If you need inherited ownership, do not add a direct relation to the Team blueprint. For more detailed information about ownership configuration, see the [ownership property section](/context-lake/business-context/ownership/.md#the-ownership-property) in the ownership documentation. ### Global VS granular permissions[​](#global-vs-granular-permissions "Direct link to Global VS granular permissions") When granting write permissions for entities of a blueprint, you have 2 levels of control: 1. Global permissions - create/update an entity as a whole. For example, allow **Member** users to update `Env` entities (all properties and relations). 2. Granular permissions - control which properties and relations a user/role can update when creating or updating an entity. For example, allow **Member** users to only update the property `slackChannelUrl` of `Env` entities. To apply granular permissions for a blueprint, use the `updateProperties` and `updateRelations` fields in the JSON, see the examples below: * Let user edit property * Let user edit entity The following change will allow **Member** users to update *only* the `slackChannelUrl` property of `Env` entities: ``` { "entities": { "updateProperties": { "slackChannelUrl": { "roles": ["Admin", "Env-moderator", "Member"], // changed from ["Admin", "Env-moderator"] "users": [], "teams": [], "ownedByTeam": false } } } } ``` If you want to apply global permissions, use the `update` field in the JSON. The following change will allow **Member** users to update *every* property/relation of `Env` entities that are owned by their team: ``` { "entities": { "update": { "roles": ["Admin", "Env-moderator"], "users": [], "teams": [], "ownedByTeam": true // changed from false } } } ``` Global permissions override granular permissions Using global permissions **overrides** any granular permissions that have been set. If both permission types are set, the global setting is used when evaluating permissions. `update`, `updateProperties` and `updateRelations` settings apply when registering new entities as well. This means that a user can't register a new entity with a property (or relation) that he doesn't have permissions to edit. --- # Govern data access Port provides granular control to ensure that every user only sees the parts of the catalog that are relevant to them. Port's catalog RBAC capabilities are enabled by utilizing [permissions controls](/platform-administration/users-and-teams/manage-users-teams/.md). Page permissions In order to manage who can view certain **pages** in Port, check out [page permissions](/interface-builder/port-interface/page/page-permissions/.md). ## Common usage[​](#common-usage "Direct link to Common usage") The catalog RBAC mechanism allows admins to finely control which users have access to specific information in the software catalog, for example: * Allow a user to edit a single specific property on an entity. * Create a fully read-only view for a developer. * Allow users to create, edit, and delete only entities owned by their team. * Restrict entity visibility to users whose team matches the entity's owner team. * Allow only users with the on-call property set to true to edit entities owned by their team. ## Set access controls for catalog data[​](#set-access-controls-for-catalog-data "Direct link to Set access controls for catalog data") The default permissions assigned to every blueprint upon creation specify that users with the `admin` role and users with the specific blueprint `moderator` role can edit the blueprint itself and manage its permissions.
See [RBAC permissions](/platform-administration/users-and-teams/manage-users-teams/.md) for more information about the different roles. The permissions described below control access to **blueprint entities and their properties**, not to the blueprint structure or permissions configuration itself. You can control who reads, creates, updates, or deletes entities, and who can view or edit specific properties within those entities. It is possible to assign global permissions controls for a blueprint's **entities** and **properties**, using the following steps: 1. Go to the [Context lake page](https://app.port.io/settings/data-model) of your portal. 2. Select your desired blueprint, click on the `...` button in the top-right corner, and select `Permissions`: 3. A dialog will open with two tabs: * **Entities tab** - set entity-level permissions (who can read, create, update, or delete entities) * **Properties tab** - set property-level permissions (who can update or view specific properties) You can also manage these permissions via the [Port API](/api-reference/update-a-blueprints-permissions/), [Terraform](https://registry.terraform.io/providers/port-labs/port-labs/latest/docs/resources/blueprint_permissions), or [Pulumi](https://www.pulumi.com/registry/packages/port/api-docs/blueprintpermissions/). Terraform and Pulumi support roles, users, teams, team ownership (`owned_by_team`), and update property/relation scopes. Advanced dynamic policies (`policy`) are not supported in those providers yet - configure them in the UI or via the API. * Entities * Properties The **Entities** tab controls who can read, create, update, or delete entities. Configure entity-level access below by role, user/team, entity ownership, or dynamic policy. * General * Basic * Advanced (entity owners) * Advanced (dynamic policy) This section allows you to assign permissions for a **role** (Member, Admin or Moderator). Click on the dropdown in the desired role row and select the permissions that will be assigned to it. ![Blueprint permission dropdown Read option](/img/software-catalog/role-based-access-control/permissions/blueprint-permission-dropdown-read.png)

**Edit via JSON (click to expand)** To edit permissions via JSON, click on the `Edit JSON` button in the top-right corner of the dialog. To give permissions to a role, add it to the `roles` array in the permission scope you desire (`read`, `register`, `unregister`, or `update`): ``` { "entities": { "read": { "roles": ["myBlueprint-moderator", "Admin", "Member"], // ...other permissions }, "register": { "roles": ["myBlueprint-moderator", "Admin", "Member"], // ...other permissions }, "unregister": { "roles": ["myBlueprint-moderator", "Admin", "Member"], // ...other permissions }, "update": { "roles": ["myBlueprint-moderator", "Admin", "Member"], // ...other permissions }, "updateProperties": { "$identifier": { "roles": ["myBlueprint-moderator", "Admin", "Member"], // ...other permissions } // ...other properties }, "updateRelations": { "service": { "roles": ["myBlueprint-moderator", "Admin", "Member"], // ...other permissions } // ...other relations } } } ``` This section allows you to assign permissions for a **user** or **team**. find a user or team If there isn't currently a row for the user or team, search for them in search box and select them. This will also automatically give them Read access. Click on the dropdown in the desired user or team row and select the permissions that will be assigned to it. ![Blueprint permission dropdown Register and Read](/img/software-catalog/role-based-access-control/permissions/blueprint-permission-dropdown-read-register.png)
"None" If you see `None` as the permission, it means that the user or team only has permissions for `updateProperties` or `updateRelations`. **Edit via JSON (click to expand)** To edit permissions via JSON, click on the `Edit JSON` button in the top-right corner of the dialog. To give permissions to a user or team, add the user or team identifier to the `users` or `teams` array in the permission scope you desire (`read`, `register`, `unregister`, or `update`): ``` { "entities": { "read": { "users": ["my-user@example.port.io"], "teams": ["my-team"], // ...other permissions }, "register": { "users": ["my-user@example.port.io"], "teams": ["my-team"], // ...other permissions }, "unregister": { "users": ["my-user@example.port.io"], "teams": ["my-team"], // ...other permissions }, "update": { "users": ["my-user@example.port.io"], "teams": ["my-team"], // ...other permissions }, "updateProperties": { "$identifier": { "users": ["my-user@example.port.io"], "teams": ["my-team"], // ...other permissions } // ...other properties }, "updateRelations": { "service": { "users": ["my-user@example.port.io"], "teams": ["my-team"], // ...other permissions } // ...other relations } } } ``` This section allows you to assign permissions based on **entity owners**. Click on the dropdown in the desired row and select the permissions that will be assigned to it. ![Blueprint permission dropdown with None selected](/img/software-catalog/role-based-access-control/permissions/blueprint-permission-dropdown-none.png)

**Edit via JSON (click to expand)** To edit permissions via JSON, click on the `Edit JSON` button in the top-right corner of the dialog. To give permissions to entity owners, set `ownedByTeam` to `true` in the permission scope you desire (`read`, `register`, `unregister`, or `update`): ``` { "entities": { "read": { "ownedByTeam": true, // ...other permissions }, "register": { "ownedByTeam": true, // ...other permissions }, "unregister": { "ownedByTeam": true, // ...other permissions }, "update": { "ownedByTeam": true, // ...other permissions }, "updateProperties": { "$identifier": { "ownedByTeam": true, // ...other permissions } // ...other properties }, "updateRelations": { "service": { "ownedByTeam": true, // ...other permissions } // ...other relations } } } ``` #### Important notes * In the context of the `register` permission, setting a user or team as an entity owner means that they can only create a new entity if they assign it to a team that they are a member of. * When entity owners permissions are configured the policy is ignored. This section allows you to assign permissions based on a **dynamic policy**. ![Blueprint permissions Add policy button](/img/software-catalog/role-based-access-control/permissions/blueprint-permissions-read-policy-button.png) **Edit via JSON (click to expand)** To edit permissions via JSON, click on the `Edit JSON` button in the top-right corner of the dialog. To configure a policy, add a `policy` query to the relevant scope. The `policy` key allows you to give **dynamic** permissions to users, by using [search queries](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules). * For **`read`**, **`update`**, and **`unregister`**: the policy filters the specific entities you are allowed to view or mutate. * For **`register`**: Port simulates the new entity and evaluates the query against it. For this reason, the `relatedTo` operator and queries against calculation/aggregation properties are not supported. You can also use [contextual query rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#contextual-query-rules) to get the context of the user executing the query. In the following example, `on-call` users are granted `read` access only to entities that share the same `region` as one of their owning teams: ``` { "entities": { // ...other scopes "read": { "policy": { "combinator": "and", "rules": [ { "property": { "context" : "user", "property": "isOnCall" }, "operator": "=", "value": "true" }, { "property": "region", "operator": "containsAny", "value": { "context" : "userTeams", "property": "region" } }, ] } // ...other permissions } } } ``` conflicting permissions When **entity owners** permissions are configured, the **dynamic policy** is ignored. The **Properties** tab restricts access to specific properties. You can control which properties are editable and which properties are visible to different users. * Update properties * Read properties Limit which properties specific users can modify. A user with update permission on a property can change its value regardless of entity-level update restrictions. Configure access by role, user, team, or entity ownership. Note: entity ownership (ownedByTeam) is supported for update properties, but dynamic policies are not. ![Update properties selection dialog](/img/software-catalog/role-based-access-control/permissions/blueprint-permission-choose-properties-and-relations.png)

``` { "entities": { "updateProperties": { "api_key": { "roles": ["platform-team", "Admin"], "users": ["my-user@example.port.io"], "teams": ["my-team"], "ownedByTeam": true } // ...other properties } } } ``` Hide specific properties from users who shouldn't see them. Users must have entity-level read access first, properties with no explicit read restrictions are visible by default. Configure access by role, user, or team. Note: only roles, users, and teams are supported for read properties. Entity ownership (ownedByTeam) and dynamic policies are not supported. ![Read properties selection dialog](/img/software-catalog/role-based-access-control/permissions/blueprint-permission-choose-read-properties.png)

``` { "entities": { "readProperties": { "api_key": { "roles": ["platform-team", "Admin"], "users": ["my-user@example.port.io"], "teams": ["my-team"] } // ...other properties } } } ``` Affected components Setting `read` permissions on entities takes effect at the API level, meaning that any component in Port that fetches entities will be affected by these permissions.
For example, a table widget that displays entities will only show entities that the user has read permissions for. ## Permission simulator[​](#permission-simulator "Direct link to Permission simulator") The permission simulator allows you to test and validate blueprint permissions before saving them.
You can simulate `read`, `register` (create), `update`, and `unregister` (delete) access for any user in your organization and quickly understand why access is granted or denied. It's available from Port AI, the UI, or the API, not only from the browser. ### Use the simulator[​](#use-the-simulator "Direct link to Use the simulator") * Port AI * UI * API Ask [Port AI](/port-ai/port-ai-assistant/.md), or your coding agent over the [Port MCP server](/agent-management/port-mcp-server/overview/.md), to run the check in plain language. For example: *"Why can Alex Chen update the checkout-service entity on the Service blueprint?"* Port AI runs the simulator and reports the exact rule that grants access, for example that Alex is on the team that owns checkout-service. *"Which services can anyone on the security-team currently update?"* Port AI returns every entity accessible under that rule, useful for checking a policy's blast radius before you widen it to more teams. 1. Go to the [Context lake page](https://app.port.io/settings/data-model), open a blueprint, and click `Permissions`. 2. In the **Permission Simulator** section at the bottom of the page, select: * A user from your organization. * An operation: `read`, `register` (create), `update`, or `unregister` (delete). 3. Review the results table to see all entities the selected user can access for the selected operation. 4. (Optional) Select a specific entity to open the detailed access breakdown. 5. In the detail view, review: * The visual flow (user → granted/denied → entity). * Which checks passed or failed. * The exact check that granted access (if access is granted). Test changes before saving The simulator evaluates pending permission changes from the form, not only the saved configuration.
This lets you verify impact in real time before applying your changes. This checks whether `alex-chen` can `update` the `checkout-service` entity on the `Service` blueprint, and why. Call `POST` on `/v1/blueprints/{blueprint_identifier}/permissions/simulate` with: ``` { "userIdentifier": "alex-chen", "operation": "update", "entityIdentifier": "checkout-service" } ``` Drop `entityIdentifier` to list every entity the user can reach for that operation, instead of checking one: ``` { "userIdentifier": "alex-chen", "operation": "update" } ``` See [Simulate permissions for a user](/api-reference/simulate-permissions-for-a-user/) for the full request and response schema. ### How permissions are evaluated[​](#how-permissions-are-evaluated "Direct link to How permissions are evaluated") The simulator evaluates five checks. Access is granted if **any one** of the following checks passes: 1. **Role check** - verifies whether the user's role is included in the operation's allowed `roles`. 2. **Owned by team** - when `ownedByTeam` is enabled, verifies whether the user belongs to at least one team in the entity's `$team`. 3. **Team list** - verifies whether the user belongs to any team listed in the operation's `teams`. 4. **User list** - verifies whether the user is explicitly listed in the operation's `users`. 5. **Policy** - verifies whether the entity matches the configured `policy` query. When access is granted, the simulator indicates the granting path, for example: "Has role `Admin` which allows this operation".
For policy-based access, the evaluated query is also displayed. ## Software catalog RBAC examples[​](#software-catalog-rbac-examples "Direct link to Software catalog RBAC examples") Refer to the [examples](/context-lake/consuming-the-lake/govern-data-access/examples/.md) page for practical examples of Port's RBAC. ## FAQ[​](#faq "Direct link to FAQ") 1What happens if a user lacks the permissions to edit a required property of the blueprint? 2What happens if the ownedByTeam setting is enabled for entity registration, but the user can't edit the team property? 3How are multiple permission checks combined? 4Do entity read permissions affect dashboards and other Port components? --- # Other consumption methods External AI agents and automation workflows can leverage Port's Context Lake to make intelligent, context-aware decisions without needing direct access to your entire toolchain. Instead of building custom integrations for each tool, external systems can query Port's unified knowledge layer to understand your organization's structure, relationships, and standards. ## n8n integration[​](#n8n-integration "Direct link to n8n integration") Port provides a custom n8n node that simplifies integration with Port's AI agents and Context Lake. To get started: 1. **[Set up Port's n8n custom node](/context-lake/consuming-the-lake/other-consumption-methods/port-n8n-node/.md)** — Install and configure the Port node in your n8n instance 2. **[Build automation workflows](/guides/all/remediate-vulnerability-with-n8n-and-port/.md)** — See an example of using Port as a context lake for vulnerability management workflows --- # Port n8n node Port provides a custom n8n node ([n8n-nodes-portio-experimental](https://www.npmjs.com/package/@port-labs/n8n-nodes-portio-experimental)) that simplifies integration with Port's AI agents and API. Instead of making manual HTTP requests for authentication, invocation, and response retrieval, the Port node handles all of this automatically, making it easy to build powerful automation workflows that leverage Port's Context Lake. This guide walks you through installing and configuring Port's custom n8n node in your n8n instance. ![n8n workflow with manual trigger and Port node](/img/guides/n8n-port-node/5-n8n-connect-port-node.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before you begin, ensure you have: * **npm installed** — Required for installing the Port n8n node package. npm comes with Node.js, [install Node.js](https://nodejs.org/) if you don't have it. * **A working n8n instance** — Either n8n Cloud or a self-hosted instance. If you don't have one, follow n8n's [guide on using Docker](https://docs.n8n.io/hosting/installation/docker/). * **Port API credentials** — Your Port `Client ID` and `Client Secret`. [Learn how to get them](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials). * **A Port account with AI features enabled** — Required for using Port AI agents in your workflows. * We recommend [creating a dedicated service account](https://docs.port.io/platform-administration/users-and-teams/manage-users-teams/#service-accounts) in Port for each workflow or agent integration. This improves security and makes it easier to manage credentials. ## Set up n8n instance[​](#set-up-n8n-instance "Direct link to Set up n8n instance") If you don't have an n8n instance running, you can set one up using one of the options below.
The location where you mount the n8n data directory is important - this is where the Port n8n node package will be installed. * npm * Docker * Docker compose If you prefer running n8n directly with npm, install it globally and start it from your terminal. 1. **Install n8n**: ``` npm install n8n -g ``` 2. **Start n8n**: ``` n8n start ``` 3. **Open n8n** — Visit `http://localhost:5678`. Using a bind mount allows the container to read/write directly to your local filesystem, making it easier to install and manage the Port n8n node package. 1. **Create a directory for n8n data** (we will call this `$n8n_HOME`): ``` mkdir -p ~/n8n-data ``` 2. **Run n8n with a bind mount**: ``` docker volume create n8n_data docker run -it --rm \ --name n8n \ -p 5678:5678 \ -e GENERIC_TIMEZONE="Africa/Accra" \ -e TZ="Africa/Accra" \ -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \ -e N8N_RUNNERS_ENABLED=true \ -e N8N_COMMUNITY_PACKAGES_ENABLED=true \ -v ~/n8n-data:/home/node/.n8n \ docker.n8n.io/n8nio/n8n ``` Configuration notes * The `N8N_COMMUNITY_PACKAGES_ENABLED=true` environment variable is required for n8n to load community-installed nodes like the Port n8n node. * Replace `"Africa/Accra"` with your preferred timezone. The `GENERIC_TIMEZONE` and `TZ` environment variables should match. * Replace `~/n8n-data` with the actual local path you want to use such as `/Users/janedoe/n8n-data`. Anything n8n writes to `/home/node/.n8n` (inside the container) will persist directly to your local directory. 3. **Access n8n** — Open your browser and navigate to `http://localhost:5678`. If you prefer using Docker Compose, create a `docker-compose.yml` file: ``` version: '3' services: n8n: image: docker.n8n.io/n8nio/n8n ports: - "5678:5678" environment: - GENERIC_TIMEZONE=Europe/Madrid - TZ=Europe/Madrid - N8N_COMMUNITY_PACKAGES_ENABLED=true volumes: - ./n8n-data:/home/node/.n8n ``` The `N8N_COMMUNITY_PACKAGES_ENABLED=true` environment variable is required for n8n to load community-installed nodes like the Port n8n node. Then run: ``` # Navigate to the directory containing your docker compose file cd /path/to/your/compose/file/directory # Pull latest version docker compose pull # Start the container docker compose up -d ``` ## Install Port's n8n node[​](#install-ports-n8n-node "Direct link to Install Port's n8n node") Now that you have n8n running, let's install Port's custom node using one of the options below. If you use n8n Cloud, the Port node is verified and available directly from the canvas. If you do not see it, [restart your workspace](#restart-n8n-cloud-workspace) and confirm that **Verified Community Nodes** is enabled in your [Admin Panel](https://app.n8n.cloud/dashboard). * Install from n8n browser UI * Install from terminal 1. In your n8n instance, go to **Settings**. 2. Select **Community Nodes**. 3. Click **Install**. 4. Enter `@port-labs/n8n-nodes-portio-experimental` as the npm package name. 5. Click **Install** to finish the setup. 1) **Navigate to your n8n data directory** (the `$n8n_HOME` directory you created earlier): ``` cd ~/n8n-data ``` 2) **Install the Port n8n node package**: ``` mkdir -p custom cd custom npm i @port-labs/n8n-nodes-portio-experimental ``` The package is installed in the `custom` folder in your n8n data directory (`$n8n_HOME`). 3) **Restart your n8n instance**: * **If using Docker**: Stop the container (`Ctrl+C` or `docker stop n8n`) and start it again with the same command. * **If using Docker Compose**: Run `docker compose restart`. Restart required n8n needs to be restarted after installing community packages so it can discover and load them. Make sure `N8N_COMMUNITY_PACKAGES_ENABLED=true` is set in your Docker configuration. The Port n8n node package is available on npm at [@port-labs/n8n-nodes-portio-experimental](https://www.npmjs.com/package/@port-labs/n8n-nodes-portio-experimental). The package has been fully approved and is ready for production use. ## Quick start[​](#quick-start "Direct link to Quick start") Now that the Port node is installed, let us verify it works by creating a simple workflow. ### Add a Port node to your workflow[​](#add-a-port-node-to-your-workflow "Direct link to Add a Port node to your workflow") 1. **Create a new workflow** in your n8n instance. ![n8n Personal workspace Create workflow button](/img/guides/n8n-port-node/1-n8n-create-workflow-button.png) 2. **Add a trigger** — For testing, add a **Manual Trigger** node so you can trigger the workflow manually by clicking the **Execute Workflow** button. ![n8n canvas with Execute workflow button](/img/guides/n8n-port-node/2-n8n-manual-trigger-workflow.png) 3. **Add the Port node**: * Click the **+** button on the left side of the screen to add a new node * Search for `port` in the node search ![n8n node search showing Port API AI result](/img/guides/n8n-port-node/3-n8n-search-port-node.png) 4. **Select a Port node** — Click the "Port API AI" search result and select "Invoke AI interaction" (this is the easiest one to test with). ![Port API AI node with Invoke AI interaction](/img/guides/n8n-port-node/4-n8n-general-node.png) 5. **Connect the nodes** — Drag from the **+** icon on the trigger node to the left side of the Port AI node to connect them. ![n8n workflow with manual trigger and Port node](/img/guides/n8n-port-node/5-n8n-connect-port-node.png) Success! You have successfully deployed your first Port n8n node! The node is now available in your n8n instance. ### Configure Port credentials[​](#configure-port-credentials "Direct link to Configure Port credentials") Before you can use the Port node, you need to add your Port API credentials. 1. **Open the Port node** — Double-click the Port AI node you just added. 2. **Create a new credential**: * At the top of the node configuration, you will see a dropdown named `Credential to connect with` * Click this dropdown and select `Create new credential` 3. **Enter your Port credentials**: * **Client ID** — Your Port Client ID * **Client Secret** — Your Port Client Secret ![Port.io credential form with Client ID and Secret](/img/guides/n8n-port-node/6-n8n-port-credentials.png) If you're not sure how to get your Port credentials, see the [Port API documentation](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials). 4. **Save the credential** — Click "Save" to store the credential. You can store multiple Port account credentials in n8n and select which one to use for each node. This is useful if you work with multiple Port organizations. ## Verify the installation[​](#verify-the-installation "Direct link to Verify the installation") To verify everything is working: 1. **Configure the Port node** — In the **General Purpose AI Interaction** node: * Select your Port credential from the dropdown * Enter a simple prompt like "List all ec2 instances in the eu-west region" * Save the node ![Port.io node parameters with user prompt field](/img/guides/n8n-port-node/7-n8n-example-question.png) 2. **Execute the workflow** — Click **Execute Workflow** to test the connection. 3. **Check the output** — If successful, you should see the AI response in the node output. ## Available operations[​](#available-operations "Direct link to Available operations") The Port n8n node provides operations for interacting with Port's AI capabilities. Each operation corresponds to a different use case for querying and automating with Port's Context Lake. `generalInvoke` Direct Port AI query. Use this operation to interact with Port's Context Lake through natural language. You can query your software catalog, get insights about your services, or leverage Port's AI to analyze your data. [API Reference](/api-reference/general-purpose-ai-interactions) `invokeAgent` Call a specific AI agent. Use this operation when you want to run predefined agent workflows configured in Port. Agents are reusable AI configurations that can be invoked with a simple prompt. [API Reference](/api-reference/invoke-a-specific-agent) `getInvocation` Retrieve results by invocation ID. Use this operation to fetch the results of a previous AI invocation. This is useful for asynchronous workflows where you need to poll for results. [API Reference](/api-reference/get-an-invocations-result) ## What's next?[​](#whats-next "Direct link to What's next?") Now that you have Port's n8n node installed and configured, you can: * **Build automation workflows** — Use Port's Context Lake to enrich your n8n workflows with organizational context * **Integrate with AI agents** — Invoke Port AI agents from n8n workflows * **Query the catalog** — Access service metadata, ownership, dependencies, and more ## Related guides[​](#related-guides "Direct link to Related guides") * [Remediate security vulnerability with n8n and Port AI](/guides/all/remediate-vulnerability-with-n8n-and-port/.md) — Example workflow using Port as a context lake for vulnerability management * [Port n8n nodes repository](https://github.com/port-labs/port-n8n-nodes) — Source code and detailed documentation for the Port n8n nodes ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Node not appearing in n8n[​](#node-not-appearing-in-n8n "Direct link to Node not appearing in n8n") * **Check package installation** — Verify that `npm i @port-labs/n8n-nodes-portio-experimental` completed successfully. Check for a `node_modules` directory in your `$n8n_HOME` directory. * **Verify environment variable** — Ensure `N8N_COMMUNITY_PACKAGES_ENABLED=true` is set in your Docker configuration. * **Check installation location** — Make sure the package is installed in the same directory that's mounted to `/home/node/.n8n` in the Docker container. * **Restart n8n** — Community packages are only loaded when n8n starts, so a restart is required after installation. ### Restart n8n Cloud workspace[​](#restart-n8n-cloud-workspace "Direct link to Restart n8n Cloud workspace") If you need to restart your hosted instance: 1. Open the [n8n Cloud admin panel](https://app.n8n.cloud/dashboard). 2. Select the **Settings** (gear) icon. 3. Scroll to **Workspace status** and select **Restart workspace**. ### Credential errors[​](#credential-errors "Direct link to Credential errors") * **Verify credentials** — Double-check that your Client ID and Client Secret are correct. * **Check AI features** — Ensure your Port account has AI features enabled. * **Test API access** — Try making a direct API call to verify your credentials work. --- # Consumption methods Consuming the lake is how developers, agents, and workflows read and use data from your context lake after it is ingested. Use this section to query the catalog, connect AI tools through the Port MCP server, govern who can see what, and trigger automations from external tools. ## Query the catalog[​](#query-the-catalog "Direct link to Query the catalog") [API access & search](/context-lake/consuming-the-lake/search-and-query/overview/.md) is Port's search and query syntax for filtering and retrieving catalog entities. Use it to power catalog pages, dashboards, scorecard rules, workflow conditions, and direct API calls. ## Consume via the Port MCP server[​](#consume-via-the-port-mcp-server "Direct link to Consume via the Port MCP server") The [Port MCP server](/context-lake/consuming-the-lake/port-mcp-server/.md) exposes the catalog to any MCP client - your IDE, an AI agent, or a custom tool - using the same tools that power [Port AI agents](/agent-management/custom-agents/overview/.md) to answer questions and take action on live catalog data. ## Consume via the Port CLI[​](#consume-via-the-port-cli "Direct link to Consume via the Port CLI") The [Port CLI](/port-ai/interfaces/port-cli/.md) is a command-line client for the Port API. Coding agents run it from the shell, print raw JSON, and pipe results through jq or a script. Platform engineers use it to export, compare, and migrate organizations. ## Govern data access[​](#govern-data-access "Direct link to Govern data access") [Governable data access](/context-lake/consuming-the-lake/govern-data-access/overview/.md) is what sets Port's RBAC apart from a static permissions list: rules are evaluated at runtime against live catalog data. You can define a policy like ***"only the payments team can see mission-critical services in the finance domain"***, and Port enforces it dynamically as ownership, criticality, and domain properties change - no manual permission updates required. ## Trigger from external tools[​](#trigger-from-external-tools "Direct link to Trigger from external tools") The [Port n8n node](/context-lake/consuming-the-lake/other-consumption-methods/port-n8n-node/.md) lets automation platforms outside Port, such as n8n, query the context lake and drive workflows without needing direct access to your toolchain. --- # Port MCP server No Port AI terms required The Port MCP server does **not** use Port AI capabilities. You can install and use it without enabling Port AI features or accepting the [Port AI Terms](https://www.port.io/legal/ai-terms). The [Port MCP server](/agent-management/port-mcp-server/overview/.md) is how LLMs, IDEs, and automated workflows **consume** your Context Lake without going through the Port UI or writing API calls by hand. Connect Cursor, Claude, GitHub Copilot, or any MCP-compatible client to Port and use natural language to query blueprints and entities, run actions, load skills, and work with the same catalog data that powers your portal. ## What you can do[​](#what-you-can-do "Direct link to What you can do") * **Query the catalog** in your IDE: ownership, dependencies, scorecard status, and entity details. * **Run Port actions** from a chat or agent session when your role allows it. * **Build and update catalog resources** with developer MCP tools (entities, actions, scorecards, and more depending on your permissions). For installation, supported clients, authentication, and the full tool list, see [Port MCP server overview and installation](/agent-management/port-mcp-server/overview/.md). ## How this relates to AI agents[​](#how-this-relates-to-ai-agents "Direct link to How this relates to AI agents") [Port AI agents](/agent-management/custom-agents/overview/.md) and the Port MCP server use the same underlying [Port MCP tools](/agent-management/port-mcp-server/available-tools/.md). The difference is mostly **who invokes them** and **where**: | | **Port MCP server** | **Port AI agents** | | --------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------ | | **Typical caller** | Developers in IDEs, external MCP clients, CI jobs | Systems, webhooks, APIs, orchestration | | **Interaction style** | Conversational, ad hoc | Domain-specific, autonomous, machine-to-machine | | **Best for** | Exploring the catalog, building in Port from your toolchain | Automated workflows (incident response, remediation, scheduled analysis) | You can use the MCP server on its own. When [Port AI agents](/agent-management/custom-agents/overview/.md) are configured, the MCP server can also invoke them, but core catalog query and action tools do not require agents. [Port AI](/port-ai/overview/.md) (assistant, API, Slack) uses the same tool surface for human-facing and programmatic AI access. ## Ingestion vs. consumption[​](#ingestion-vs-consumption "Direct link to Ingestion vs. consumption") | Direction | Interface | Purpose | | ----------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------- | | **Ingest** | [Port MCP in ingestion](/context-lake/ingestion/ingest-data-into-port/other/port-mcp/.md) | Create or update entities from an agent or IDE workflow | | **Consume** | Port MCP server (this page) | Read and act on catalog data from external AI tools | For live data that is not modeled in the catalog, pair MCP consumption with [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md). See [Integrations vs. MCP connectors](/context-lake/ingestion/integrations-vs-mcp/.md). ## Where to go next[​](#where-to-go-next "Direct link to Where to go next") * [Port MCP server overview and installation](/agent-management/port-mcp-server/overview/.md). * [Available MCP tools](/agent-management/port-mcp-server/available-tools/.md). * [Port AI agents](/agent-management/custom-agents/overview/.md). * [Token-based authentication for CI/CD](/agent-management/port-mcp-server/token-based-authentication/.md). --- # Advanced ### Search route query parameters[​](#search-route-query-parameters "Direct link to Search route query parameters") The search route also supports several query parameters that affect the returned output: | Parameter | Description | Available values | Default value | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------- | ------------- | | `attach_title_to_relation` | `true`: Both the identifier and the title of the related Entity will appear under the Relation key

`false`: Only the identifier of the related Entity will appear under the Relation key | `true`/`false` | `false` | | `exclude_calculated_properties` | Should [mirror properties](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md) and [calculation properties](/context-lake/data-model/setup-blueprint/properties/calculation-property/.md) be returned with the result | `true`/`false` | `false` | #### `attach_title_to_relation` example[​](#attach_title_to_relation-example "Direct link to attach_title_to_relation-example") Here are examples outputs based on the value of the `attach_title_to_relation` parameter: * True * False Here is a search response with `attach_title_to_relation=true`: ``` { "ok": true, "matchingBlueprints": [ "region", "deployment", "vm", "microservice", "k8sCluster", "permission", "runningService" ], "entities": [ { "identifier": "e_vb9EPyW1zOamcbT1", "title": "cart-deployment", "blueprint": "deployment", "team": ["Team BE"], "properties": { "version": "1.4", "user": "yonatan", "status": "failed", "github-action-url": "https://a.com", "Region": "AWS" }, "relations": { "microservice": { "identifier": "e_47MwTvQj03MpVyBx", "title": "admin-test" } }, "createdAt": "2022-07-27T17:11:04.344Z", "createdBy": "auth0|6278b02000955c006f9132d3", "updatedAt": "2022-07-27T17:11:04.344Z", "updatedBy": "auth0|6278b02000955c006f9132d3" } ] } ``` Here is the same search response with `attach_title_to_relation=false`: ``` { "ok": true, "matchingBlueprints": [ "region", "deployment", "vm", "microservice", "k8sCluster", "permission", "runningService" ], "entities": [ { "identifier": "e_vb9EPyW1zOamcbT1", "title": "cart-deployment", "blueprint": "deployment", "team": ["Team BE"], "properties": { "version": "1.4", "user": "yonatan", "status": "failed", "github-action-url": "https://a.com", "Region": "AWS" }, "relations": { "microservice": "e_47MwTvQj03MpVyBx" }, "createdAt": "2022-07-27T17:11:04.344Z", "createdBy": "auth0|6278b02000955c006f9132d3", "updatedAt": "2022-07-27T17:11:04.344Z", "updatedBy": "auth0|6278b02000955c006f9132d3" } ] } ``` --- # Examples This page provides practical examples of using Port's search and query syntax across different features. ## Catalog pages[​](#catalog-pages "Direct link to Catalog pages") Initial filters for [catalog pages](/interface-builder/port-interface/page/catalog-page/.md) are configured when creating or editing a catalog page in your [software catalog](https://app.getport.io/organization/catalog). These filters pre-filter which entities appear when the page loads, improving performance and showing only relevant data. Filters are particularly useful for blueprints with large entity counts (such as Snyk vulnerabilities), where requests may take a long time to ld or even time out without filtering. * Basic example * Advanced example This example demonstrates how to filter a Snyk vulnerability catalog page, showing only `high` and `critical` severity vulnerabilities: ``` { "combinator": "and", "rules": [ { "value": [ "high", "critical" ], "property": "severity", "operator": "in" } ] } ``` This example demonstrates how to filter a service catalog page with multiple conditions, showing only production services owned by specific teams that were updated within a specific date range: ``` { "combinator": "and", "rules": [ { "value": "production", "property": "environment", "operator": "=" }, { "value": [ "platform_team", "developers_team" ], "property": "$team", "operator": "containsAny" }, { "property": "last_push", "operator": "between", "value": { "from": "", "to": "" } } ] } ``` ## Entity pages[​](#entity-pages "Direct link to Entity pages") Filters for related entity tabs are configured when creating custom tabs on any [entity page](/interface-builder/port-interface/page/entity-page/.md). To add filters, navigate to the **Related Entities** tab, click the `+` button to create a new tab or edit an existing one. Then under **Initial filters**, click the filters button to add your query. These filters are particularly useful when a tab displays a large number of related entities. Filters improve performance and make the data more manageable. * Basic example * Advanced example This example demonstrates how to filter related pull requests for a repository entity, showing only PRs with an `open` status: ``` { "combinator": "and", "rules": [ { "value": "open", "property": "status", "operator": "=" } ] } ``` This example demonstrates how to filter related PagerDuty incidents for a service entity with multiple conditions, showing only high-urgency incidents that are still unresolved and were created in the last week: ``` { "combinator": "and", "rules": [ { "value": "high", "property": "urgency", "operator": "=" }, { "value": [ "triggered", "acknowledged" ], "property": "status", "operator": "in" }, { "property": "created_at", "operator": "between", "value": { "preset": "lastWeek" } } ] } ``` ## Dashboard widgets[​](#dashboard-widgets "Direct link to Dashboard widgets") Widget-specific filters are configured in the widget creatio or edit form for [dashboard widgets](/interface-builder/port-interface/dashboards/overview/.md). These filters determine which entities are included in calculations and visualizations. * Basic property filter * Filter by contextual data * Filter by relation-path This example demonstrates how to filter overdue pull requests, showing only PRs with an `open` status that were created in the **past 90 days**: ``` { "combinator": "and", "rules": [ { "value": "open", "property": "status", "operator": "=" }, { "property": "createdAt", "operator": "between", "value": { "preset": "last3Months" } } ] } ``` JSON mode required Contextual queries using the `context` field require JSON mode. You cannot configure these through the UI form. **Filter by logged-in user** This example demonstrates how to filter entities owned by the logged-in user: ``` { "combinator": "and", "rules": [ { "operator": "matchAny", "property": { "path": ["relationID_1"] }, "value": { "context": "user", "property": "$identifier" } } ] } ``` **Filter by user's teams** This example demonstrates how to filter entities by the logged-in user's owning teams: ``` { "combinator": "and", "rules": [ { "value": { "context": "user", "property": "$team" }, "property": "$team", "operator": "containsAny" } ] } ``` This example demonstrates how to filter pull requests based on their relationship to a specific repository. It shows only PRs belonging to the `backend-api` service: ``` { "combinator": "and", "rules": [ { "operator": "matchAny", "property": { "path": [ "service" ] }, "value": "backend_api" } ] } ``` ## Blueprint read permissions[​](#blueprint-read-permissions "Direct link to Blueprint read permissions") Blueprint read permissions can be configured with dynamic `policy` rules using search and query syntax. Configure these in the [data model](https://app.port.io/settings/data-model) page by clicking on a blueprint and going to the **Permissions** tab. The `policy` key allows you to create dynamic read access rules that grant users access to entities based on their properties, team membership, and contextual user data. This determines which entities users can see in catalog pages. For detailed examples and information, see the [read permissions policy section](/context-lake/consuming-the-lake/govern-data-access/overview/.md#set-global-access-controls-to-catalog-data) in the catalog RBAC documentation. ## Aggregation properties[​](#aggregation-properties "Direct link to Aggregation properties") Configure filters when creating or editing an [aggregation property](/context-lake/data-model/setup-blueprint/properties/aggregation-property/.md) in the [data model](https://app.getport.io/settings/data-model) page. The search and query filter is defined under the `query` section of the aggregation property. This example demonstrates how to add an aggregation property to a `service` blueprint that calculates the number of open high-severity vulnerabilities for each service, filtering only vulnerabilities with `severity` set to `high` and `status` set to `open`: ``` { "aggregationProperties": { "numberOfOpenHighVulnerabilities": { "title": "Number of open high severity vulnerabilities", "target": "vulnerability", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "severity", "operator": "=", "value": "high" }, { "property": "status", "operator": "=", "value": "open" } ] } } } } ``` ## Scorecards[​](#scorecards "Direct link to Scorecards") Configure filters when creating or editing a scorecard. [Filters](/governance/standards-and-compliance/concepts-and-structure/.md#filter-elements) determine which entities the rule evaluates, filtering out irrelevant entities and performing checks only on entities that pass the filtering. Scorecards use conditions Scorecard filters use `conditions` instead of `rules`, but follow the same structure with `combinator`, `property`, `operator`, and `value`. This example demonstrates how to filter a scorecard to apply only to production services owned by specific teams. This ensures the quality check only runs on critical services: ``` "filter": { "combinator": "and", "conditions": [ { "value": [ "builder_team", "developers_team" ], "operator": "containsAny", "property": "$team" }, { "value": "production", "operator": "=", "property": "environment" } ] } ``` Supported operators For a complete list of the available operators in scorecard filters, see the available operators section under the [rule elements](/governance/standards-and-compliance/concepts-and-structure/.md#rule-elements) documentation. ## Self-service actions[​](#self-service-actions "Direct link to Self-service actions") Configure these options when creating or editing an action in the [self-service actions](https://app.port.io/self-serve) page. * Action conditions * Entity input datasets * Dynamic permissions Action conditions determine which entities an action appears on, ensuring actions are only available for relevant entities. Configure this in the **Basic Details** tab of the action form. DAY-2 and DELETE only Action conditions are only available for DAY-2 and DELETE operations. This example demonstrates how to conditionally display an action based on entity properties. It shows the action only on entities where the `environment` property equals `production`: ``` { "type": "SEARCH", "rules": [ { "operator": "=", "property": "environment", "value": "production" } ], "combinator": "and" } ``` Filter dropdown options in action forms to show only relevant entities based on properties, relations, or user context. To configure this, go to the **User Form** tab when setting up entity-type inputs, then expand the [advanced configuration](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/advanced-form-configurations/.md) section where you can define a dataset filter. This is particularly useful for filtering entity options based on the logged-in user or their teams. This example demonstrates how to filter an entity input dropdown to show only environments owned by the logged-in user's teams. This ensures users can only select environments they have access to: ``` { "properties": { "environment": { "type": "string", "format": "entity", "blueprint": "environment", "dataset": { "combinator": "and", "rules": [ { "property": "team_mirror", // Need to add a mirror property of the Owning team "operator": "in", // to the environment blueprint "value": { "jqQuery": ".user.team" } } ] } } } } ``` [Dynamic permissions](/workflows/actions-and-automations/create-self-service-experiences/set-self-service-actions-rbac/dynamic-permissions/.md) control who can execute or approve actions based on entity properties and user context. When creating or editing an action, go to the **Permissions** tab to configure query-based permissions. For examples of dynamic action permissions using search and query syntax, see the [examples](/workflows/actions-and-automations/create-self-service-experiences/set-self-service-actions-rbac/dynamic-permissions/examples/.md) page. ## API[​](#api "Direct link to API") You can use search and query syntax with the following API routes: [search entities](/api-reference/search-entities/) and [search a blueprint's entities](/api-reference/search-a-blueprints-entities/). United States region The examples below use the EU region API.
If you wish to use the US region API, replace `https://api.port.io` with `https://api.us.port.io`. The following examples provide a foundation for using the search route. You can modify the `rules` array to match your specific search requirements. * Python * Javascript * cURL ``` # Dependencies to install: # $ python -m pip install requests import json import requests CLIENT_ID = "YOUR_CLIENT_ID" CLIENT_SECRET = "YOUR_CLIENT_SECRET" API_URL = "https://api.port.io/v1" credentials = {"clientId": CLIENT_ID, "clientSecret": CLIENT_SECRET} token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) access_token = f"Bearer {token_response.json()['accessToken']}" # You can now use the value in access_token when making further requests headers = { 'Authorization': access_token } query = { "combinator": "or", "rules": [ { "property": "$title", "operator": "=", "value": "admin-prod" }, { "property": "$title", "operator": "=", "value": "admin-test" } ] } search_req = requests.post(f"{API_URL}/entities/search", headers=headers, json=query) search_entities = search_req.json()['entities'] for entity in search_entities: print(json.dumps(entity)) ``` ``` // Dependencies to install: // $ npm install axios --save const axios = require("axios").default; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const API_URL = "https://api.port.io/v1"; (async () => { const tokenResp = await axios.post(`${API_URL}/auth/access_token`, { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, }); const accessToken = tokenResp.data.accessToken; // You can now use the value in accessToken when making further requests const config = { headers: { Authorization: `Bearer ${accessToken}`, }, }; const query = { combinator: "or", rules: [ { property: "$title", operator: "=", value: "admin-prod", }, { property: "$title", operator: "=", value: "admin-test", }, ], }; const response = await axios.post( `${API_URL}/entities/search`, query, config ); console.log(response.data["entities"]); })(); ``` ``` #!/bin/bash # Dependencies to install: # For apt: # $ sudo apt-get install jq # For yum: # $ sudo yum install jq access_token=$(curl --location --request POST 'https://api.port.io/v1/auth/access_token' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }' | jq '.accessToken' | sed 's/"//g') # The token will be available in the access_token variable curl --location --request POST 'https://api.port.io/v1/entities/search?attach_title_to_relation=true&exclude_calculated_properties=false' \ --header "Authorization: Bearer $access_token" \ --header 'Content-Type: application/json' \ --data-raw '{ "combinator": "or", "rules": [ { "property": "$title", "operator": "=", "value": "admin-prod" }, { "property": "$title", "operator": "=", "value": "admin-test" } ] }' ``` --- # Comparison operators This page details the available comparison operators when writing [rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) as part of the search route. ### = (Equal)[​](#-equal "Direct link to = (Equal)") The `=` operator checks exact matches of the specified value: ``` { "operator": "=", "property": "myProperty", "value": "myExactValue" } ``` This operator can also be used to check the value of `boolean` properties: ``` { "operator": "=", "property": "myBooleanProperty", "value": true } ``` For datetime properties, the `=` operator only supports ISO8601 format strings: ``` { "operator": "=", "property": "$createdAt", "value": "2022-07-26T16:38:06.839Z" } ``` DateTime format support The `=` operator only supports ISO8601 format strings for datetime values.
To use preset date ranges (like "lastWeek", "today", etc.), use the [between](#between) or [notBetween](#notbetween) operators instead. ### != (Not Equal)[​](#-not-equal "Direct link to != (Not Equal)") The `!=` operator checks exact matches of the specified value and returns all results that fail to satisfy the check: ``` { "operator": "!=", "property": "myProperty", "value": "myExactValue" } ``` This operator can also be used to check the value of `boolean` properties: ``` { "operator": "!=", "property": "myBooleanProperty", "value": false } ``` Not supported on array properties The `=` and `!=` operators are **not supported** on array-type properties and may produce unexpected or incorrect results. For array properties, use [`containsAny`](#containsany), [`isEmpty`](#isempty), or [`isNotEmpty`](#isnotempty) instead. However, the `!=` and [`notIn`](#notin) operators work correctly on `string` and enum-valued properties to exclude specific values from results. ### > (Greater Than)[​](#-greater-than "Direct link to > (Greater Than)") The `>` operator checks values larger than the specified value: ``` { "operator": ">", "property": "myNumericProperty", "value": 7 } ``` ### >= (Greater Than or Equal)[​](#-greater-than-or-equal "Direct link to >= (Greater Than or Equal)") The `>=` operator checks values larger than or equal to the specified value: ``` { "operator": ">=", "property": "myNumericProperty", "value": 7 } ``` ### < (Less Than)[​](#-less-than "Direct link to < (Less Than)") The `<` operator checks values less than the specified value: ``` { "operator": "<", "property": "myNumericProperty", "value": 7 } ``` ### <= (Less Than or Equal)[​](#-less-than-or-equal "Direct link to <= (Less Than or Equal)") The `<=` operator checks values less than or equal to the specified value: ``` { "operator": "<=", "property": "myNumericProperty", "value": 7 } ``` ### isEmpty[​](#isempty "Direct link to isEmpty") The `isEmpty` operator checks if the value of the specified property is `null`: ``` { "operator": "isEmpty", "property": "myProperty" } ``` ### isNotEmpty[​](#isnotempty "Direct link to isNotEmpty") The `isNotEmpty` operator checks if the value of the specified property is not `null`: ``` { "operator": "isNotEmpty", "property": "myProperty" } ``` ### propertySchema[​](#propertyschema "Direct link to propertySchema") The `propertySchema` filter can be used with any standard operator. It allows you to filter entities based on a properties matching a specific type (for example, find all string properties with a given value): * String * URL ``` { "propertySchema": { "type": "string" }, "operator": "=", "value": "My value" } ``` ``` { "propertySchema": { "type": "string", "format": "url" }, "operator": "=", "value": "https://example.com" } ``` Using propertySchema in search rules * The `propertySchema` can be used with any Port [property](/context-lake/data-model/setup-blueprint/properties/overview/.md#supported-properties). * The `propertySchema` replaces the `property` filter when performing property schema search. ### between[​](#between "Direct link to between") The `between` operator checks datetime values and returns entities whose relevant datetime property matches the given range: ``` { "operator": "between", "property": "$createdAt", "value": { "preset": "lastWeek" } } ``` **Available Presets:** * `last3Years` - In the past 3 years * `last2Years` - In the past 2 years * `last12Months` - In the past 365 days * `last6Months` - In the past 180 days * `last3Months` - In the past 90 days * `lastMonth` - In the past 30 days * `last2Weeks` - In the past 2 weeks * `lastWeek` - In the past week * `lastDay` - In the past day * `today` - Today * `tomorrow` - Tomorrow * `yesterday` - Yesterday * `nextDay` - In the next day * `nextWeek` - In the next week * `next2Weeks` - In the next 2 weeks * `nextMonth` - In the next 30 days * `next3Months` - In the next 90 days * `next6Months` - In the next 180 days * `next12Months` - In the next 365 days When using the catalog page UI, these presets are available via the `is` operator when filtering datetime properties. Future date presets availability The future date presets (`nextDay`, `nextWeek`, `next2Weeks`, `nextMonth`, `next3Months`, `next6Months`, `next12Months`) are only available in the [catalog page](/interface-builder/port-interface/page/catalog-page/.md) filters and initial filters. They are not supported in table widgets or other locations. The `between` operator also supports standard date ranges: ``` { "combinator": "and", "rules": [ { "operator": "between", "property": "$createdAt", "value": { "from": "2022-07-26T16:38:06.839Z", "to": "2022-07-29T17:00:28.006Z" } } ] } ``` ### notBetween[​](#notbetween "Direct link to notBetween") The `notBetween` operator checks datetime values and returns entities whose relevant datetime property does not match the given range: ``` { "operator": "notBetween", "property": "$createdAt", "value": { "preset": "lastWeek" } } ``` ### contains[​](#contains "Direct link to contains") The `contains` operator checks if the specified value exists within a **string** property: ``` { "property": "myStringProperty", "operator": "contains", "value": "mySubString" } ``` ### doesNotContains[​](#doesnotcontains "Direct link to doesNotContains") The `doesNotContains` operator checks if the specified value **does not** exists in the specified property: ``` { "operator": "doesNotContains", "property": "myStringProperty", "value": "otherValue" } ``` ### containsAny[​](#containsany "Direct link to containsAny") The `containsAny` operator checks if **any** of the specified strings exist in the target **array**: ``` { "operator": "containsAny", "property": "myArrayProperty", "value": ["Value1", "Value2", ...] } ``` ### beginsWith[​](#beginswith "Direct link to beginsWith") The `beginsWith` operator checks if the specified property starts with the specified value: ``` { "operator": "beginsWith", "property": "myStringProperty", "value": "myString" } ``` ### doesNotBeginsWith[​](#doesnotbeginswith "Direct link to doesNotBeginsWith") The `doesNotBeginsWith` operator checks if the specified property **does not** start with the specified value: ``` { "operator": "doesNotBeginsWith", "property": "myStringProperty", "value": "otherValue" } ``` ### endsWith[​](#endswith "Direct link to endsWith") The `endsWith` operator checks if the specified property ends with the specified value: ``` { "operator": "endsWith", "property": "myStringProperty", "value": "myString" } ``` ### doesNotEndsWith[​](#doesnotendswith "Direct link to doesNotEndsWith") The `doesNotEndsWith` operator checks if the specified property **does not** end with the specified value: ``` { "operator": "doesNotEndsWith", "property": "myStringProperty", "value": "otherValue" } ``` ### in[​](#in "Direct link to in") The `in` operator checks if a `string` property is equal to one or more specified `string` values: * Standard * Dynamic Filter ``` { "property": "myStringProperty", "operator": "in", "value": ["Value1", "Value2"] } ``` In order to filter entities that **belong to one or more of your teams** you can use the special `My Teams` filter: ``` { "property": "$team", "operator": "in", "value": ["My Teams"] } ``` You can also use the `My Teams` filter in the UI: ![Catalog filter configured to show scorecards owned by any of the current user\'s teams](/assets/images/MyTeamsFilter-7b6d29f57461891c5ee5ebd78391d19b.png) ### notIn[​](#notin "Direct link to notIn") The `notIn` operator checks if a `string` property is **not equal** to all of the specified `string` values: ``` { "property": "myStringProperty", "operator": "notIn", "value": ["Value1", "Value2"] } ``` --- # Relation operators This page details the available relation operators when writing [rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) as part of the search route. ## RelatedTo[​](#relatedto "Direct link to RelatedTo") The `relatedTo` operator will return all entities that have a relationship with the specified entity: ### Structure[​](#structure "Direct link to Structure") | Field | Description | | ----------- | ------------------------------------------------------------------------------------------ | | `operator` | Search operator to use when evaluating this rule, see a list of available operators below. | | `blueprint` | Blueprint of the entity identifier specified in the `value` field. | | `value` | Value to filter by. | ``` { "operator": "relatedTo", "blueprint": "myBlueprint", "value": "myEntity" } ``` The operator also supports multiple related entities as the searched value: ``` { "operator": "relatedTo", "blueprint": "myBlueprint", "value": ["myFirstEntity", "mySecondEntity"] } ``` This query will return all of the entities that are related to one or more of the identifiers in the value array. ### Required[​](#required "Direct link to Required") The `relatedTo` operator also supports the `required` property - which allows you to search for: * Related entities from all relations (relations with either required `true` or `false`). * Related entities only from required relations (relations with required `true`). * Related entities only from non-required relations (relations with required `false`). For example, to search only for related entities that *require* the `myEntity` entity from the `myBlueprint` blueprint, use the following search rule: ``` { "operator": "relatedTo", "required": true, "value": "myEntity", "blueprint": "myBlueprint" } ``` ### Direction[​](#direction "Direct link to Direction") The `relatedTo` operator also supports the `direction` property - which allows you to search for dependent entities in a specific direction on the dependency graph. To better understand the functionality of this property, let's take a look at the example below: Let's assume that we have the blueprints `deploymentConfig` and `microservice` with the following relation definition (declared on the `deploymentConfig` blueprint): ``` "relations": { "microservice": { "description": "The service this Deployment Config belongs to", "many": false, "required": false, "target": "microservice", "title": "Microservice" } } ``` In addition, we have the following entities: ``` Deployment Configs: - Order-Service-Production - Cart-Service-Production Microservices: - Order Service - Cart Service Environments: - Production ``` And the following relations: ``` Order-Service-Production -> Order-Service Order-Service-Production -> Production Cart-Service-Production -> Cart-Service Cart-Service-Production -> Production ``` By looking at the resulting graph layout, we can also map the directions: ![Dependency graph upstream downstream diagram](/assets/images/search-direction-diagram-faab8361638f4c4f6c67294958fe94bb.png) * To search for entities which the source depends on - use `"direction": "upstream"`. * To search for entities which depend on the source - use `"direction": "downstream"`. In the example shown above, if we want to get the `Microservice` and `Environment` that *Order-Service-Production* depends on, the search rule would be: ``` { "operator": "relatedTo", "blueprint": "deploymentConfig", "value": "Order-Service-Production", "direction": "upstream" } ``` And the result shall be: **Order-Service-Production upstream related entities (click to expand)** ``` { "ok": true, "matchingBlueprints": ["microservice", "environment"], "entities": [ { "identifier": "Order-Service", "title": "Order-Service", "blueprint": "microservice", "properties": { "on-call": "mor@getport.io", "language": "Python", "slack-notifications": "https://slack.com/Order-Service", "launch-darkly": "https://launchdarkly.com/Order-Service" }, "relations": {}, "createdAt": "2022-11-17T15:54:20.432Z", "createdBy": "auth0|62ab380295b34240aa511cdb", "updatedAt": "2022-11-17T15:54:20.432Z", "updatedBy": "auth0|62ab380295b34240aa511cdb" }, { "identifier": "Production", "title": "Production", "blueprint": "environment", "properties": { "awsRegion": "eu-west-1", "configUrl": "https://github.com/config-labs/kube/config.yml", "slackChannel": "https://yourslack.slack.com/archives/CHANNEL-ID", "onCall": "Mor P", "namespace": "Production" }, "relations": {}, "createdAt": "2022-09-19T08:54:23.025Z", "createdBy": "Cnc3SiO7T0Ld1y1u0BsBZFJn0SCiPeLS", "updatedAt": "2022-10-16T09:28:32.960Z", "updatedBy": "auth0|62ab380295b34240aa511cdb" } ] } ``` If we want to get all of the `deploymentConfigs` that are deployed in the *Production* `Environment`, the search rule would be: ``` { "operator": "relatedTo", "blueprint": "environment", "value": "Production", "direction": "downstream" } ``` And the result shall be: **Production downstream related entities (click to expand)** ``` { "ok": true, "matchingBlueprints": ["deploymentConfig"], "entities": [ { "identifier": "Order-Service-Production", "title": "Order-Service-Production", "blueprint": "deploymentConfig", "properties": { "url": "https://github.com/port-labs/order-service", "config": { "encryption": "SHA256" }, "monitor-links": [ "https://grafana.com", "https://prometheus.com", "https://datadog.com" ] }, "relations": { "microservice": "Order-Service", "environment": "Production" }, "createdAt": "2022-11-17T15:55:55.591Z", "createdBy": "auth0|62ab380295b34240aa511cdb", "updatedAt": "2022-11-17T15:55:55.591Z", "updatedBy": "auth0|62ab380295b34240aa511cdb" }, { "identifier": "Cart-Service-Production", "title": "Cart-Service-Production", "blueprint": "deploymentConfig", "properties": { "url": "https://github.com/port-labs/cart-service", "config": { "foo": "bar" }, "monitor-links": [ "https://grafana.com", "https://prometheus.com", "https://datadog.com" ] }, "relations": { "microservice": "Cart-Service", "environment": "Production" }, "createdAt": "2022-11-17T15:55:10.714Z", "createdBy": "auth0|62ab380295b34240aa511cdb", "updatedAt": "2022-11-17T15:55:20.253Z", "updatedBy": "auth0|62ab380295b34240aa511cdb" } ] } ``` ## MatchAny[​](#matchany "Direct link to MatchAny") The `matchAny` operator will match entities based on your input: * If you specify a single value, it will find all entities with the same identifier. * If you provide a list of values, it will match any entity whose identifier is in the list. ### Related to by specific path[​](#related-to-by-specific-path "Direct link to Related to by specific path") You can search for entities that are related through a specific path of relations. This is useful when you want to find entities that are connected through a specific chain of relationships. ### Structure[​](#structure-1 "Direct link to Structure") | Field | Description | | ------------------------ | -------------------------------------------------------------------------------------------------------------------- | | `property.path` | An array containing the full path of relation identifiers to traverse. | | `property.fromBlueprint` | *(Optional)* The blueprint to start the path traversal from. If omitted, traversal starts from the target blueprint. | | `operator` | The search operator to use. For this feature, use `"matchAny"`. | | `value` | The value or list of values to match against the target entity identifiers at the end of the path. | #### For upstream paths[​](#for-upstream-paths "Direct link to For upstream paths") ``` { "property": { "path": ["relation1", "relation2", "relation3"] }, "operator": "matchAny", "value": "targetEntity" } ``` #### For downstream paths[​](#for-downstream-paths "Direct link to For downstream paths") ``` { "property": { "path": ["relation1", "relation2", "relation3"], "fromBlueprint": "sourceBlueprint" }, "operator": "matchAny", "value": "targetEntity" } ``` When using downstream paths, the `fromBlueprint` parameter specifies the source blueprint from which to start the path traversal. Instead of thinking about the path as downstream from the target, we treat it as upstream from the specified blueprint to the target blueprint. This means that the path will be traversed starting from entities of the specified `fromBlueprint`. ### Examples[​](#examples "Direct link to Examples") Suppose you have the following data model: ![Data model path from service to cluster through deployedOn and from cluster to deployment through deployments](/img/software-catalog/search-in-port/specific-path-diagram-example.png) #### Find all services related to a cluster (upstream) To find all services that are related to a specific cluster (e.g., "production-cluster"): ``` { "combinator": "and", "rules": [ { "property": "$blueprint", "operator": "=", "value": "service" }, { "property": { "path": ["deployedOn"] }, "operator": "matchAny", "value": "production-cluster" } ] } ``` This will return all **services** that have a deployment in the "production-cluster". *** #### Example 2: Find all deployments related to a specific service (downstream) To find all deployments related to a specific service (e.g., "production-service"): ``` { "combinator": "and", "rules": [ { "property": "$blueprint", "operator": "=", "value": "deployment" }, { "property": { "path": ["deployedOn", "deployments"], "fromBlueprint": "service" }, "operator": "matchAny", "value": "production-service" } ] } ``` This will return all **deployments** that are related to the "production-service". *** #### Contextual values[​](#contextual-values "Direct link to Contextual values") You can combine relation-path filters with [contextual query rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#contextual-query-rules) to dynamically filter entities based on the current user. This is useful when you need to filter entities based on ownership derived from related entities. The following rule filters entities where the owner is derived from a **directly related entity**: ``` { "combinator": "and", "rules": [ { "operator": "matchAny", "property": { "path": ["relationID_1"] }, "value": { "context": "user", "property": "$identifier" } } ] } ``` This will return all entities where the related entity (via `relationID_1`) matches the current user's identifier. You can also traverse **multiple relations** to find ownership. The following rule filters entities where the owner is derived by traversing through two related entities: ``` { "combinator": "and", "rules": [ { "operator": "matchAny", "property": { "path": ["relationID_1", "relationID_2"] }, "value": { "context": "user", "property": "$identifier" } } ] } ``` This will return all entities where the entity at the end of the relation chain (traversing `relationID_1` → `relationID_2`) matches the current user's identifier. In the above examples: * `property.path` specifies the chain of relations to traverse (replace the relation names with your actual relation identifiers). * The contextual `value` dynamically resolves to the current user's identifier. * The `matchAny` operator matches entities where the value at the end of the relation path equals the contextual value. ## Self-relation[​](#self-relation "Direct link to Self-relation") Self-relations allow a blueprint to reference itself, so entities of the same type can be connected. ![Squad, Tribe, Domain, Group hierarchy diagram](/img/guides/hierarchyTiers/hierarchyTiers.png)

In this example, a **group** is comprised of one or more **domains**, which are comprised of one or more **tribes**, and so on.
The diagram illustrates the hierarchy, where different entities are linked by the same self-relation (parent) within the `Team` blueprint. You can use the `matchAny` operator to search through self-relations, allowing you to find entities at various levels of the hierarchy. ### Examples[​](#examples-1 "Direct link to Examples") **Example 1: basic self-relation with fixed hops count** If you want **exactly 2 hops**, specify the self-relation twice: ``` { "combinator": "and", "rules": [ { "property": { "path": [ "parent", "parent" ], "fromBlueprint" : "Team" }, "operator": "matchAny", "value": "targetEntity" } ] } ``` For example, if the `value` is `Squad`: * First hop: `Squad` → `Tribe`. * Second hop: `Tribe` → `Domain`. Result: `Domain` (only the final destination after exactly 2 hops). *** **Example 2: self-relation with maxHops for variable hops** If you want a variable number of hops (between 1 and 15), use `maxHops`.
`maxHops` specifies the maximum number of hops to traverse through the self-relation. ``` { "combinator": "and", "rules": [ { "property": { "path": [ { "relation": "parent", "maxHops": 3 } ], "fromBlueprint": "Team" }, "operator": "matchAny", "value": "targetEntity" } ] } ``` For example, if the `value` is `Squad`: * First hop: `Squad` → `Tribe`. * Second hop: `Tribe` → `Domain`. * Third hop: `Domain` → `Group`. Result: `Tribe`, `Domain` and `Group` (all entities found along the path up to 3 hops). maxHops limitation The `maxHops` parameter can only be applied **once** per path, and it accepts values from **1 to 15**. *** **Example 3: mixed approach** You can also mix fixed hops with variable hops. For example, if you specify the self-relation twice followed by a `maxHops` object, the system will start traversing from the 2 hops already made and continue with the additional hops specified in `maxHops`. ``` { "combinator": "and", "rules": [ { "property": { "path": [ "parent", "parent", { "relation": "parent", "maxHops": 3 } ], "fromBlueprint": "Team" }, "operator": "matchAny", "value": "targetEntity" } ] } ``` For example, if the `value` is `Squad`: * First hop: `Squad` → `Tribe`. * Second hop: `Tribe` → `Domain`. * Third hop: `Domain` → `Group`. Result: `Domain` (from fixed hops) and `Group` (from variable hops, up to 3 additional hops). --- # API access & search Port's search and query syntax provides a powerful way to filter and retrieve data from your software catalog. You can use this syntax across the Port product to dynamically filter entities, control permissions, configure automations, and more. ## Where to use search & query?[​](#where-to-use-search--query "Direct link to Where to use search & query?") You can use the search and query syntax in the following areas: * **Catalog pages** - Initial filters for pages and tables. * **Entity pages** - Filters for custom related entity tabs. * **Dashboard widgets** - Dashboard-level filters and widget-specific filters for [data widgets](/interface-builder/port-interface/dashboards/data-widgets/.md). * **Blueprint read permissions** - Dynamic `policy` rules to control which entities users can see. * **Aggregation properties** - Filters to determine which entities are included in [aggregation calculations](/context-lake/data-model/setup-blueprint/properties/aggregation-property/.md). * **Scorecards** - Filters to determine which entities a rule evaluates. * **Self-service actions** - Action conditions, entity input datasets, and dynamic permissions. * **API** - Search endpoints ([search entities](/api-reference/search-entities/) and [search a blueprint's entities](/api-reference/search-a-blueprints-entities/)). For practical examples of using search & query in each of these areas, see the [examples](/context-lake/consuming-the-lake/search-and-query/examples/.md) page. ## Common queries usage[​](#common-queries-usage "Direct link to Common queries usage") High quality search is essential to effectively track assets in your software catalog. Using Port's search you can: * Find all running services that are not healthy. * List all libraries that have known vulnerabilities. * Filter all services running in a specific cluster (in a query or self service form). * Catalog initial filters based on the logged in user's properties. ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/consuming-the-lake/search-and-query/examples/.md) page for practical code snippets for search. ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/consuming-the-lake/search-and-query/advanced/.md) page for advanced search use cases and outputs. --- # Structure & syntax This page details the structure and syntax used to build search queries in Port, including how to construct search requests, define rules, use combinators, and apply operators to filter entities. ## Search request[​](#search-request "Direct link to Search request") A search request contains filters and rules to find matching [entities]() in your software catalog.
To search for entities using the API, see the [search](/api-reference/search-a-blueprints-entities/) route. A search request is comprised of the following elements: | Field | Description | | ------------ | ---------------------------------------------------------- | | `combinator` | Defines the logical operation to apply to the query rules. | | `rules` | An array of search rules to filter results with. | For example: ``` { "combinator": "and", "rules": [ { "property": "$blueprint", "operator": "=", "value": "myBlueprint" }, { "property": "$identifier", "operator": "contains", "value": "myIdentifierPart" } ] } ``` The query above searches for all entities based on the `myBlueprint` blueprint whose `identifier` contains the string `myIdentifierPart`. ## Combinator[​](#combinator "Direct link to Combinator") There are two available combinators: * `and` - will apply a logical AND operation between all rules, requiring all of them to satisfy for a given asset in order to return it. * `or` - will apply a logical OR operation between all rules, requiring at least one of them to satisfy for a given asset in order to return it. single rule queries If you only have a single rule in your query, the combinator has no effect. But keep in mind that it still needs to be included to adhere to the query structure. **Single rule query example (click to expand)** In the following example, only a single rule appears in the `rules` array, so the `combinator` field has no effect: ``` { "combinator": "and", "rules": [ { "property": "$blueprint", "operator": "=", "value": "myBlueprint" } ] } ``` * And * Or ``` { "combinator": "and", "rules": [ { "property": "$blueprint", "operator": "=", "value": "myBlueprint" }, { "property": "$identifier", "operator": "contains", "value": "myIdentifierPart" } ] } ``` ``` { "combinator": "or", "rules": [ { "property": "$blueprint", "operator": "=", "value": "myBlueprint" }, { "property": "$identifier", "operator": "contains", "value": "myIdentifierPart" } ] } ``` ### Nested queries[​](#nested-queries "Direct link to Nested queries") Rules can be nested, An item in the `rules` array can itself be a `{combinator, rules}` object, allowing you to build complex boolean logic: ``` { "combinator": "or", "rules": [ { "combinator": "and", "rules": [ { "property": "language", "operator": "=", "value": "Python" }, { "property": "tier", "operator": "=", "value": "production" } ] }, { "combinator": "and", "rules": [ { "property": "language", "operator": "=", "value": "Go" }, { "property": "tier", "operator": "=", "value": "critical" } ] } ] } ``` The query above matches entities that are **either** (Python AND production) **or** (Go AND critical). No short-circuit evaluation All rules in a query are evaluated simultaneously and there is no short-circuit logic. For example, if the top-level combinator is `"or"` with five nested rule groups, all five groups are evaluated for every entity even if the first group already matches. The `"or"` combinator means an entity is included in the results if **at least one** group matches, but it does not skip evaluating the remaining groups. ## Rules[​](#rules "Direct link to Rules") A search rule is a small filtering unit, used to control the search output. Here is an example search rule: ``` { "property": "$blueprint", "operator": "=", "value": "microservice" } ``` Port has 2 types of search rule operators: 1. Comparison (e.g. `=`, `>`). 2. Relation (e.g. `relatedTo`). ### Comparison operators[​](#comparison-operators "Direct link to Comparison operators") #### Structure[​](#structure "Direct link to Structure") | Field | Description | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `operator` | Search operator to use when evaluating this rule, see a list of available operators below | | `property` | Property to filter by according to its value. It can be a [meta-property](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md) such as `$identifier`, or one of the [standard properties](/context-lake/data-model/setup-blueprint/properties/overview/.md#available-properties) | | `value` | The value to filter by | #### Operators[​](#operators "Direct link to Operators") A wide variety of operators are available, see them [here](/context-lake/consuming-the-lake/search-and-query/operators/comparison-operators/.md). *** ### Relation operators[​](#relation-operators "Direct link to Relation operators") Several relation-based operators are available, see them [here](/context-lake/consuming-the-lake/search-and-query/operators/relation-operators/.md). ### Contextual query rules[​](#contextual-query-rules "Direct link to Contextual query rules") Contextual query rules inject the logged-in user's identity into a query at evaluation time, allowing you to access that user's properties and/or owning teams. You can mix contextual query rules freely with other rules as part of your queries. #### Where contextual query rules are supported[​](#where-contextual-query-rules-are-supported "Direct link to Where contextual query rules are supported") | Location | Supported | Notes | | ----------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Catalog page initial filters | ✅ | Filter entities shown on a catalog page based on the viewer | | Dashboard widget filters | ✅ | Scope chart/table widgets to the logged-in user | | Self-service action `dataset` rules | ✅ | Filter entities in an [entity input](/workflows/actions-and-automations/create-self-service-experiences/setup-ui-for-action/user-inputs/entity/.md) dropdown | | API search requests | ✅ | Context is derived from the API token's associated user | Use contextual rules instead of dynamic properties The older [dynamic properties](#dynamic-properties) syntax (`{{getUserTeams()}}`) is deprecated. Contextual query rules are the replacement, they work in all the same places **including** self-service action inputs and API requests. #### How to use contextual query rules[​](#how-to-use-contextual-query-rules "Direct link to How to use contextual query rules") This can be used in either the `property` or `value` key in a query rule: * Property * Value ``` { ...other rule keys "property": { "context": "user" | "userTeams", "property": "prop" } } ``` ``` { ...other rule keys "value": { "context": "user" | "userTeams", "property": "prop" } } ``` #### Available contexts[​](#available-contexts "Direct link to Available contexts") | Context | Description | | ----------- | ----------------------------------------------------------------- | | `user` | The entity of the user triggering the query | | `userTeams` | The entities of the owning teams of the user triggering the query | #### Usage examples[​](#usage-examples "Direct link to Usage examples") ![Filter panel with contextual search rules](/img/entities-search/contextual-search-rules.png) The following rule will result in the entities owned by any one of the user's teams: ``` [ ...other rules { "property": "$team", "operator": "containsAny", "value": { "context": "userTeams", "property": "$identifier" } } ] ``` The following rule will result in entities with the same department as the user's: ``` [ ...other rules { "property": "department", "operator": "=", "value": { "context": "user", "property": "department" } } ] ``` The following rule asserts that only users with `manager` role will get the resulting entities: ``` [ ...other rules { "property": { "context": "user", "property": "company_role" }, "operator": "=", "value": "manager" } ] ``` The following rule asserts that only users in the user's team/s will get the resulting entities: ``` [ ...other rules { "property": { "context": "userTeams", "property": "$identifier" }, "operator": "containsAny", "value": ["Spider Team", "Builder Team"] } ] ``` For examples of using contextual values with relation path filters, see [filter with contextual values](/context-lake/consuming-the-lake/search-and-query/operators/relation-operators/.md#filter-with-contextual-values). ### Filter by relations/scorecards[​](#filter-by-relationsscorecards "Direct link to Filter by relations/scorecards") When using the [search a blueprint's entities](/api-reference/search-a-blueprints-entities/) API route, you can also filter results by [relations]() or [scorecards](). See the following examples for each filter type: * Relation * Scorecard * Scorecard rule ``` { "relation": "relationId", "operator": "=", "value": "value" } ``` ``` { "scorecard": "scorecardId", "operator": "=", "value": "Bronze" } ``` ``` { "scorecard": "scorecardId", "scorecardRule": "scorecardRuleId", "operator": "=", "value": "Bronze" } ``` ### Dynamic properties[​](#dynamic-properties "Direct link to Dynamic properties") Deprecated, use contextual query rules instead Dynamic properties are deprecated and will be discontinued soon. Use [contextual query rules](#contextual-query-rules) instead, which work in more places and provide access to the full user/team entity. When using Port's UI, you can use properties of the logged-in user when writing rules by using the following functions: * `getUserTeams` - a list of the teams the user belongs to. * `getUserEmail` - the user's email. * `getUserFullName` - the user's full name. * `blueprint` - the blueprint identifier of the current page. UI only & not supported in self-service actions These functions are evaluated **only** in Port's UI context: [catalog page](/interface-builder/port-interface/page/catalog-page/.md#page-creation) initial filters and [dashboard widget](/interface-builder/port-interface/dashboards/custom-widgets/.md#chart-filters) filters. They are **not** evaluated in self-service action schemas (e.g., `dataset` rules on entity inputs) or API requests. Using them there will yield empty values. For action inputs, use [contextual query rules](#contextual-query-rules) instead. #### Usage examples[​](#usage-examples-1 "Direct link to Usage examples") ``` [ { "property": "$team", "operator": "containsAny", "value": ["{{getUserTeams()}}"] } ] ``` ``` [ { "property": "emails", "operator": "contains", "value": "{{getUserEmail()}}" } ] ``` ``` [ { "property": "name", "operator": "=", "value": "{{getUserFullName()}}" } ] ``` ``` [ { "property": "$blueprint", "operator": "=", "value": "{{blueprint}}" } ] ``` --- # Data model [YouTube video player](https://www.youtube.com/embed/5-JE7EUKcf4)
Your data model is the schema layer of your [context lake](/context-lake/overview/.md) - it defines what the things your organization cares about are called, what properties describe them, and how they relate to one another. Combined with [data ingestion](/context-lake/ingestion/overview/.md), which populates that schema with real, live entities, the data model turns your software catalog into an **organizational semantic layer**: something AI agents, workflows, and dashboards can query and act on with full context. ## Blueprints, properties, and relations[​](#blueprints-properties-and-relations "Direct link to Blueprints, properties, and relations") [Blueprints](/context-lake/data-model/setup-blueprint/overview/.md) are the basic building block of your data model - each one represents an asset in your organization, such as a microservice, a Kubernetes cluster, or a cloud account. A blueprint is made up of [properties](/context-lake/data-model/setup-blueprint/properties/overview/.md), the customizable fields that hold the data ingested for that asset, and connected to other blueprints through [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md), the logical connections between them (for example, a service *depends on* a package, or a deployment *runs in* an environment). Port's [plug & play integrations](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#available-plug--play-integrations) come with predefined blueprints, properties, and relations out of the box, but you can freely edit them or create your own to match your organization's structure. Editing the data model can be done from the UI, Port's API, or simply by describing the change in natural language through [Port's AI assistant](/port-ai/port-ai-assistant/.md) or an agent connected via the [Port MCP server](/agent-management/port-mcp-server/overview/.md). ## Define your ontology[​](#define-your-ontology "Direct link to Define your ontology") A schema alone tells Port what fields exist. An [ontology](/context-lake/data-model/define-your-ontology/.md) tells it what those fields *mean* - through descriptions on blueprints and properties, semantic relation titles, and well-chosen property types. This is what lets AI agents reason about your catalog correctly instead of just reading raw values. ## Manage your data model as code[​](#manage-your-data-model-as-code "Direct link to Manage your data model as code") If you prefer to version-control your data model alongside your infrastructure, Port's data model can be defined and managed using [Terraform](/context-lake/data-model/iac/terraform-managed-blueprint/.md) or [Pulumi](/context-lake/data-model/iac/pulumi-managed-blueprint/.md), in addition to the UI, API, and Port AI. --- # Define your ontology In Port, your data model is more than a schema - it is an ontology. A schema tells a system what fields exist. An ontology tells a system what those fields *mean*. This distinction matters enormously for AI agents. When an agent queries your context lake, it reads descriptions, relation titles, and property types to understand your organization's structure and make decisions. A schema without semantic meaning is a structure humans can browse. An ontology with semantic meaning is a knowledge graph agents can traverse and act on. This page covers various elements that give your context lake ontological meaning: ## Blueprint and property descriptions[​](#blueprint-and-property-descriptions "Direct link to Blueprint and property descriptions") The `description` field on a blueprint and on each of its properties is how you communicate intent to agents and to future humans reading the model. Create in Port ``` { "identifier": "service", "title": "Service", "description": "A deployable unit of software owned by a team. Represents a microservice, backend API, or frontend application tracked in the catalog.", "schema": { "properties": { "on_call": { "type": "string", "format": "user", "title": "On-call engineer", "description": "The engineer currently on-call for this service. Updated automatically from PagerDuty." }, "tier": { "type": "string", "title": "Service tier", "description": "Business criticality of this service. Tier 1 = customer-facing revenue path. Tier 2 = internal platform dependency. Tier 3 = non-critical tooling.", "enum": ["tier-1", "tier-2", "tier-3"] } } } } ``` **Write descriptions as if explaining to a new team member who has no context.** The agent has exactly the same level of familiarity. * The blueprint description should explain *what this entity represents* in your organization, not just what the word means generically. * Property descriptions should explain *what the value means*, where it comes from, and any implicit conventions (for example, which team owns it, or what the expected format is). These descriptions are read by Port AI when it queries the catalog, by custom agents when they plan their next action, and by external agents connecting via the MCP server. They are also rendered in the UI when a user hovers over the info icon on a property. ## Relation titles and descriptions[​](#relation-titles-and-descriptions "Direct link to Relation titles and descriptions") Relations are the edges of your knowledge graph. The `title` you give a relation is the semantic label of that edge — it is how agents understand the connection between two blueprints. The `description` explains why the edge exists and what it means to traverse it. ``` { "relations": { "owned_by": { "title": "Owned by", "description": "The team responsible for this service's development and on-call. Used to route incidents, enforce ownership-based permissions, and calculate team workload.", "target": "team", "required": false, "many": false }, "runs_in": { "title": "Runs in", "description": "The cloud environment (production, staging, dev) where this service is currently deployed. Many-to-many: a service can run in multiple environments simultaneously.", "target": "environment", "required": false, "many": true }, "depends_on": { "title": "Depends on", "description": "Upstream services this service calls at runtime. Used to calculate blast radius during incidents and map the impact of a degraded dependency.", "target": "service", "required": false, "many": true } } } ``` **Titles** provide the semantic label. Compare these two for the same structural edge: | Vague title | Semantic title | | ----------- | -------------- | | `team` | `Owned by` | | `env` | `Runs in` | | `svc` | `Depends on` | The vague titles are technically correct but semantically silent. When an agent asks *"which team is responsible for the payment service?"*, it needs to traverse the `Owned by` edge — not figure out that `team` might mean ownership. **Descriptions** resolve ambiguity that a title alone cannot. A service can *own*, *use*, *deploy to*, or *report to* a team in different contexts — all of which would share the same target blueprint. The description is what tells an agent which kind of connection this is, when to traverse it, and what it means in your organization's domain. ## Property types as semantic signals[​](#property-types-as-semantic-signals "Direct link to Property types as semantic signals") Choosing a property type is not just a validation decision — it is a semantic declaration. Port offers [several property types](/context-lake/data-model/setup-blueprint/properties/overview/.md#supported-properties), and each one signals something different about the value. | When you use this type | The agent understands | | ---------------------- | --------------------------------------------------------------------------------------------------------------- | | `team` | This value is a Port team — it has members, a manager, and can be used for ownership and permission resolution. | | `user` | This value is a Port user — it has an email, role, and team memberships. | | `url` | This value is a navigable link to an external resource (dashboard, repository, runbook). | | `boolean` | This is a binary flag. The agent can use it as a condition without parsing string values. | | `number` | This is a numeric measure. The agent can compare, rank, and threshold on it. | | `string` | This is free-form text. The agent cannot make structural assumptions about it. | Using `string` for a value that is structurally a team name, a URL, or a numeric count deprives the agent of the semantic signal it needs to reason correctly. An agent that sees a `team`-typed `owned_by` property can immediately resolve ownership, query that team's properties, and apply team-based permissions. An agent that sees a `string` field named `owner` has to guess. The same principle applies to format specifiers like `date-time`, `email`, and `ipv4`: they narrow the value's meaning and give agents precise handling rules. ## Enum values with descriptions[​](#enum-values-with-descriptions "Direct link to Enum values with descriptions") Enums define the vocabulary of a field. Without a description, an agent can only infer meaning from the value string itself. ``` { "severity": { "type": "string", "title": "Severity", "description": "Impact level of this incident on users and revenue. critical = complete service outage or data loss. high = major feature unavailable, no workaround. medium = degraded performance, workaround exists. low = cosmetic or minor issue, no user impact.", "enum": ["critical", "high", "medium", "low"] } } ``` Put the per-value meaning directly in the property `description`. Explain: * What each value represents in your organization's conventions. * What conditions or thresholds map to each value. * How the value is set (manually, automatically, by an SLA policy, etc.). An agent asked to *"escalate all critical incidents in the payment service"* can only act correctly if it knows that `critical` means complete outage, not just "bad". An agent asked to *"create an incident with appropriate severity"* needs the vocabulary to make that judgment. *** Taken together, these elements transform your context lake from a record store into a knowledge graph. Every description you write, every relation title you choose, every property type you select, and every enum definition you annotate is a piece of ontological signal that makes the graph richer for both agents and humans. For the technical fields and configuration options, see [set up blueprints](/context-lake/data-model/setup-blueprint/overview/.md), [properties](/context-lake/data-model/setup-blueprint/properties/overview/.md), and [relate blueprints](/context-lake/data-model/setup-blueprint/relate-blueprints/.md). --- # Pulumi-Managed Blueprint Example This example includes a complete [blueprint](/context-lake/data-model/setup-blueprint/overview/.md) resource definition in Pulumi, which includes: * [Blueprint](/context-lake/data-model/setup-blueprint/overview/.md?definition=pulumi#configure-blueprints-in-port) definition examples. * All [property](/context-lake/data-model/setup-blueprint/properties/overview/.md) type definitions. * [Relation](/context-lake/data-model/setup-blueprint/relate-blueprints/.md?definition=pulumi#configure-relations-in-port) definition example. * [Mirror property](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md) definition example. * [Calculation property](/context-lake/data-model/setup-blueprint/properties/calculation-property/.md) definition example. Here is the example definition in all the supported languages: * Python * TypeScript * JavaScript * GO ``` from pulumi import ResourceOptions import port_pulumi as port other = port.Blueprint( "other", identifier="test-docs-relation", icon="Microservice", title="Test Docs Relation", properties=port.BlueprintPropertiesArgs( string_props={ "myStringProp": port.BlueprintPropertiesStringPropsArgs( title="My string", required=False ) } ), ) blueprint = port.Blueprint( "myBlueprint", identifier="test-docs", icon="Microservice", title="Test Docs", properties=port.BlueprintPropertiesArgs( string_props={ "myStringProp": port.BlueprintPropertiesStringPropsArgs( title="My string", required=False ), "myUrlProp": port.BlueprintPropertiesStringPropsArgs( title="My url", required=False, format="url" ), "myEmailProp": port.BlueprintPropertiesStringPropsArgs( title="My email", required=False, format="email" ), "myUserProp": port.BlueprintPropertiesStringPropsArgs( title="My user", required=False, format="user" ), "myTeamProp": port.BlueprintPropertiesStringPropsArgs( title="My team", required=False, format="team" ), "myDatetimeProp": port.BlueprintPropertiesStringPropsArgs( title="My datetime", required=False, format="date-time" ), "myTimerProp": port.BlueprintPropertiesStringPropsArgs( title="My timer", required=False, format="timer" ), "myYAMLProp": port.BlueprintPropertiesStringPropsArgs( title="My yaml", required=False, format="yaml" ), }, number_props={ "myNumberProp": port.BlueprintPropertiesNumberPropsArgs( title="My number", required=False, ) }, boolean_props={ "myBooleanProp": port.BlueprintPropertiesBooleanPropsArgs( title="My boolean", required=False ) }, object_props={ "myObjectProp": port.BlueprintPropertiesObjectPropsArgs( title="My object", required=False ) }, array_props={ "myArrayProp": port.BlueprintPropertiesArrayPropsArgs( title="My array", required=False ) } ), mirror_properties={ "myMirrorProp": port.BlueprintMirrorPropertiesArgs( title="My mirror property", path="myRelation.myStringProp" ), "myMirrorPropWithMeta": port.BlueprintMirrorPropertiesArgs( title="My mirror property of meta property", path="myRelation.$identifier" ), }, calculation_properties={ "myCalculation": port.BlueprintCalculationPropertiesArgs( title="My calculation property", calculation=".properties.myStringProp + .properties.myStringProp", type="string", ), "myCalculationWithMeta": port.BlueprintCalculationPropertiesArgs( title="My calculation property with meta properties", calculation='.identifier + "-" + .title + "-" + .properties.myStringProp', type="string", ), }, relations={ "myRelation": port.BlueprintRelationsArgs( title="My relation", target="test-docs-relation", many=False, required=False, ), }, opts=ResourceOptions(depends_on=[other]), ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; const other = new port.Blueprint("other", { identifier: "test-docs-relation", icon: "Microservice", title: "Test Docs Relation", properties: { stringProps: { myStringProp: { title: "My string", required: false, }, }, }, }); const myBlueprint = new port.Blueprint( "myBlueprint", { identifier: "test-docs", icon: "Microservice", title: "Test Docs", properties: { stringProps: { myStringProp: { title: "My string", required: false, }, myUrlProp: { title: "My url", required: false, format: "url", }, myEmailProp: { title: "My email", required: false, format: "email", }, myUserProp: { title: "My user", required: false, format: "user", }, myTeamProp: { title: "My team", required: false, format: "team", }, myDatetimeProp: { title: "My datetime", required: false, format: "date-time", }, myTimerProp: { title: "My timer", required: false, format: "timer", }, myYAMLProp: { title: "My yaml", required: false, format: "yaml", }, }, numberProps: { myNumberProp: { title: "My number", required: false, }, }, booleanProps: { myBooleanProp: { title: "My boolean", required: false, }, }, objectProps: { myObjectProp: { title: "My object", required: false, }, }, arrayProps: { myArrayProp: { title: "My array", required: false, }, }, }, mirrorProperties: { myMirrorProp: { title: "My mirror property", path: "myRelation.myStringProp", }, myMirrorPropWithMeta: { title: "My mirror property of meta property", path: "myRelation.$identifier", }, }, calculationProperties: { myCalculation: { title: "My calculation property", calculation: ".properties.myStringProp + .properties.myStringProp", type: "string", }, myCalculationWithMeta: { title: "My calculation property with meta properties", calculation: '.identifier + "-" + .title + "-" + .properties.myStringProp', type: "string", }, }, relations: { myRelation: { title: "My relation", target: "test-docs-relation", many: false, required: false, }, }, }, { dependsOn: [other] } ); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const other = new port.Blueprint("other", { identifier: "test-docs-relation", icon: "Microservice", title: "Test Docs Relation", properties: { stringProps: { myStringProp: { title: "My string", required: false, }, }, }, }); const myBlueprint = new port.Blueprint( "myBlueprint", { identifier: "test-docs", icon: "Microservice", title: "Test Docs", properties: { stringProps: { myStringProp: { title: "My string", required: false, }, myUrlProp: { title: "My url", required: false, format: "url", }, myEmailProp: { title: "My email", required: false, format: "email", }, myUserProp: { title: "My user", required: false, format: "user", }, myTeamProp: { title: "My team", required: false, format: "team", }, myDatetimeProp: { title: "My datetime", required: false, format: "date-time", }, myTimerProp: { title: "My timer", required: false, format: "timer", }, myYAMLProp: { title: "My yaml", required: false, format: "yaml", }, }, numberProps: { myNumberProp: { title: "My number", required: false, }, }, booleanProps: { myBooleanProp: { title: "My boolean", required: false, }, }, objectProps: { myObjectProp: { title: "My object", required: false, }, }, arrayProps: { myArrayProp: { title: "My array", required: false, }, }, }, mirrorProperties: { myMirrorProp: { title: "My mirror property", path: "myRelation.myStringProp", }, myMirrorPropWithMeta: { title: "My mirror property of meta property", path: "myRelation.$identifier", }, }, calculationProperties: { myCalculation: { title: "My calculation property", calculation: ".properties.myStringProp + .properties.myStringProp", type: "string", }, myCalculationWithMeta: { title: "My calculation property with meta properties", calculation: '.identifier + "-" + .title + "-" + .properties.myStringProp', type: "string", }, }, relations: { myRelation: { title: "My relation", target: "test-docs-relation", many: false, required: false, }, }, }, { dependsOn: [other] } ); ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { other, err := port.NewBlueprint(ctx, "other", &port.BlueprintArgs{ Identifier: pulumi.String("test-docs-relation"), Icon: pulumi.String("Microservice"), Title: pulumi.String("Test Docs Relation"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myStringProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My string"), Required: pulumi.Bool(false), }, }, }, }) if err != nil { return err } myBlueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("test-docs"), Icon: pulumi.String("Microservice"), Title: pulumi.String("Test Docs"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myStringProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My string"), Required: pulumi.Bool(false), }, "myUrlProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My url"), Required: pulumi.Bool(false), Format: pulumi.String("url"), }, "myEmailProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My email"), Required: pulumi.Bool(false), Format: pulumi.String("email"), }, "myUserProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My user"), Required: pulumi.Bool(false), Format: pulumi.String("user"), }, "myTeamProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My team"), Required: pulumi.Bool(false), Format: pulumi.String("team"), }, "myDatetimeProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My datetime"), Required: pulumi.Bool(false), Format: pulumi.String("date-time"), }, "myTimerProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My timer"), Required: pulumi.Bool(false), Format: pulumi.String("timer"), }, "myYAMLProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My yaml"), Required: pulumi.Bool(false), Format: pulumi.String("yaml"), }, }, NumberProps: port.BlueprintPropertiesNumberPropsMap{ "myNumberProp": port.BlueprintPropertiesNumberPropsArgs{ Title: pulumi.String("My number"), Required: pulumi.Bool(false), }, }, BooleanProps: port.BlueprintPropertiesBooleanPropsMap{ "myBooleanProp": port.BlueprintPropertiesBooleanPropsArgs{ Title: pulumi.String("My boolean"), Required: pulumi.Bool(false), }, }, ObjectProps: port.BlueprintPropertiesObjectPropsMap{ "myObjectProp": port.BlueprintPropertiesObjectPropsArgs{ Title: pulumi.String("My object"), Required: pulumi.Bool(false), }, }, ArrayProps: port.BlueprintPropertiesArrayPropsMap{ "myArrayProp": port.BlueprintPropertiesArrayPropsArgs{ Title: pulumi.String("My array"), Required: pulumi.Bool(false), }, }, }, MirrorProperties: port.BlueprintMirrorPropertiesMap{ "myMirrorProp": port.BlueprintMirrorPropertiesArgs{ Title: pulumi.String("My mirror property"), Path: pulumi.String("myRelation.myStringProp"), }, "myMirrorPropWithMeta": port.BlueprintMirrorPropertiesArgs{ Title: pulumi.String("My mirror property of meta property"), Path: pulumi.String("myRelation.$identifier"), }, }, CalculationProperties: port.BlueprintCalculationPropertiesMap{ "myCalculation": port.BlueprintCalculationPropertiesArgs{ Title: pulumi.String("My calculation property"), Calculation: pulumi.String(".properties.myStringProp + .properties.myStringProp"), Type: pulumi.String("string"), }, "myCalculationWithMeta": port.BlueprintCalculationPropertiesArgs{ Title: pulumi.String("My calculation property with meta properties"), Calculation: pulumi.String(".identifier + \"-\" + .title + \"-\" + .properties.myStringProp"), Type: pulumi.String("string"), }, }, Relations: port.BlueprintRelationsMap{ "myRelation": &port.BlueprintRelationsArgs{ Title: pulumi.String("My relation"), Target: pulumi.String("test-docs-relation"), Many: pulumi.Bool(false), Required: pulumi.Bool(false), }, }, }, pulumi.DependsOn([]pulumi.Resource{other})) if err != nil { return err } return nil }) } ``` --- # Terraform-Managed Blueprint Example This example includes a complete [blueprint](/context-lake/data-model/setup-blueprint/overview/.md) resource definition in Terraform, which includes: * [Blueprint](/context-lake/data-model/setup-blueprint/overview/.md?definition=tf#configure-blueprints-in-port) definition examples. * All [property](/context-lake/data-model/setup-blueprint/properties/overview/.md) type definitions. * [Relation](/context-lake/data-model/setup-blueprint/relate-blueprints/.md?definition=tf#configure-relations-in-port) definition example. * [Mirror property](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md) definition example. * [Calculation property](/context-lake/data-model/setup-blueprint/properties/calculation-property/.md) definition example. Here is the example definition. Set your Port [credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) as environment variables and add the configuration to a `.tf` file. ``` export PORT_CLIENT_ID="" export PORT_CLIENT_SECRET="" ``` In your `.tf` file, configure the `port-labs/port-labs` provider and define the blueprint resource: **Terraform blueprint example (click to expand)** ``` terraform { required_providers { port = { source = "port-labs/port-labs" version = "~> 2.0.3" } } } provider "port" { base_url = "https://api.port.io" } resource "port_blueprint" "myBlueprint" { depends_on = [ port_blueprint.other ] # ...blueprint properties identifier = "test-docs" icon = "Microservice" title = "Test Docs" properties = { string_props = { "myStringProp" = { title = "My string" required = false } "myUrlProp" = { title = "My url" required = false format = "url" } "myEmailProp" = { title = "My email" required = false format = "email" } "myUserProp" = { title = "My user" required = false format = "user" } "myTeamProp" = { title = "My team" required = false format = "team" } "myDatetimeProp" = { title = "My datetime" required = false format = "date-time" } "myYAMLProp" = { title = "My YAML" required = false format = "yaml" } "myTimerProp" = { title = "My timer" required = false format = "timer" } } number_props = { "myNumberProp" = { title = "My number" required = false } } boolean_props = { "myBooleanProp" = { title = "My boolean" required = false } } object_props = { "myObjectProp" = { title = "My object" required = false } } array_props = { "myArrayProp" = { title = "My array" required = false } } } mirror_properties = { "myMirrorProp" = { title = "My mirror property" path = "myRelation.myStringProp" } "myMirrorPropWithMeta" = { title = "My mirror property of meta property" path = "myRelation.$identifier" } } calculation_properties = { "myCalculation" = { title = "My calculation property" calculation = ".properties.myStringProp + .properties.myStringProp" type = "string" } # Calculation property making use of meta-properties "myCalculationWithMeta" = { title = "My calculation property with meta properties" calculation = ".identifier + \"-\" + .title + \"-\" + .properties.myStringProp" type = "string" } } relations = { "myRelation" = { target = port_blueprint.other.identifier title = "myRelation" many = false required = false } } } resource "port_blueprint" "other" { # ...blueprint properties identifier = "test-docs-relation" icon = "Microservice" title = "Test Docs Relation" properties = { string_props = { "myStringProp" = { title = "My string" required = false } } } } ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` --- # Default blueprints Every Port account comes with a set of default [blueprints](), used to represent common concepts in your organization.
See the list below for a description of each blueprint. You can view and edit blueprints in the [data model page](https://app.port.io/settings/data-model) of your portal. ### ![Service icon](/img/icons/service.svg) Service[​](#-service "Direct link to -service") `service` - a flexible [blueprint]() representing a piece of software that is owned by a team/group in your organization. This blueprint serves as a single component with rich context about all the resources related to the software, such as: * **The code itself** - a repository or a specific folder in a monorepo. * **Incident management** components, such as a PagerDuty service. * **Code scanning** components, such as Snyk Targets or Sonar Cloud projects. * **Project management** components, such as Jira projects and issues. * **Runtime** components, such as workloads. Services are the core component of R\&D operations within a developer portal, providing each team with a unified view of its services and their status across different domains, such as **security**, **stability**, **access management**, and more. ### ![Environment icon](/img/icons/environment.svg) Environment[​](#-environment "Direct link to -environment") `environment` - represents a deployment environment in your organization. In addition to this [blueprint](), 3 default [entities]() will be created: `Production`, `Staging`, and `Test`.
You can modify the default environments and/or create additional ones. ### ![Workload icon](/img/icons/workload.svg) Workload[​](#-workload "Direct link to -workload") `workload` - represents a `service` running in a specific `environment`, with context of the relevant related components. For example, a `frontend` service in your organization can have `frontend-prod` and `frontend-test` workloads, each with its own Kubernetes namespace, Sentry project, and owning team. ### Deployment[​](#deployment "Direct link to Deployment") `deployment` - represents a production deployment, created each time a PR is merged to the default branch. Deployment entities provide traceability from code change to production, helping you track delivery frequency and link deployments back to the services and teams responsible for them. ### ![User icon](/img/guides/icons/User.svg)![User icon](/img/guides/icons/dark/User.svg) User[​](#-user "Direct link to -user") `_user` - represents a user in your organization. This blueprint can be related to other "user" blueprints coming from your connectors, such as `github_user`, allowing you to manage a user across multiple tools from a single component. ### ![Team icon](/img/guides/icons/Team.svg)![Team icon](/img/guides/icons/dark/Team.svg) Team[​](#-team "Direct link to -team") `_team` - represents a team in your organization. This blueprint can be related to other "team" blueprints coming from your connectors, such as `github_team`, allowing you to manage a team across multiple tools from a single component. Users in Port can be assigned to teams, allowing you to implement ownership of components in your portal. ### ![Organization icon](/img/guides/icons/Organization.svg)![Organization icon](/img/guides/icons/dark/Organization.svg) Organization[​](#-organization "Direct link to -organization") `organization` - a logical grouping of teams and services in your catalog. Organizations represent higher-level business units in your company hierarchy, making it easier to manage ownership and visibility across multiple teams and their services. ### ![Scorecards icon](/img/guides/icons/Scorecards.svg)![Scorecards icon](/img/guides/icons/dark/Scorecards.svg) Scorecard[​](#-scorecard "Direct link to -scorecard") `_scorecard` - represents a scorecard in Port. Scorecards enable you to define and track metrics and standards for entities in your catalog. Each scorecard evaluates entities based on a set of rules and assigns them a level, such as `Basic`, `Bronze`, `Silver`, or `Gold`. ### ![ScorecardRule icon](/img/guides/icons/ScorecardRule.svg)![ScorecardRule icon](/img/guides/icons/dark/ScorecardRule.svg) Scorecard rule[​](#-scorecard-rule "Direct link to -scorecard-rule") `_rule` - represents an individual rule within a scorecard. Each rule defines one or more conditions that an entity must meet to reach a specific level in its scorecard. ### ![ScorecardRuleResult icon](/img/guides/icons/ScorecardRuleResult.svg)![ScorecardRuleResult icon](/img/guides/icons/dark/ScorecardRuleResult.svg) Scorecard rule result[​](#-scorecard-rule-result "Direct link to -scorecard-rule-result") `_rule_result` - represents the evaluation result of a scorecard rule for a specific entity. Rule results track whether each entity passes or fails each scorecard rule, giving you a clear view of where entities stand and what needs to be improved. ### ![AiAgent icon](/img/guides/icons/AiAgent.svg)![AiAgent icon](/img/guides/icons/dark/AiAgent.svg) AI agent[​](#-ai-agent "Direct link to -ai-agent") `_ai_agent` - represents an AI agent registered in your portal. AI agents are the primary interface for interacting with Port using natural language. This blueprint tracks the agents available in your organization. ### AI invocation[​](#ai-invocation "Direct link to AI invocation") `_ai_invocations` - represents a single interaction with an AI agent. Each time an agent is invoked, a corresponding AI invocation entity is created, providing a record of agent usage. Permissions on this blueprint control which users in your organization can access Port AI features. ### MCP server[​](#mcp-server "Direct link to MCP server") `_mcp_server` - represents an external MCP (Model Context Protocol) server connected to Port. MCP servers extend Port's AI capabilities by providing additional tools and context that AI agents can use. Registering MCP servers in Port allows AI agents to discover and use their tools dynamically. ### ![Workflows icon](/img/guides/icons/Workflows.svg)![Workflows icon](/img/guides/icons/Workflows.svg) Workflow[​](#-workflow "Direct link to -workflow") `_workflow` - represents a workflow defined in your portal. Every workflow is automatically synced to this blueprint as an entity, giving you a catalog view of all workflows across your organization. The `_workflow` blueprint supports team ownership, so you can assign owning teams to workflows and control access using Port's standard blueprint permissions model. See [workflow catalog and ownership](/workflows/permissions-and-rbac/workflow-catalog/.md) for details. ## System blueprints reference[​](#system-blueprints-reference "Direct link to System blueprints reference") Port includes protected system blueprints (identifiers starting with `_`) that power core platform features. These blueprints have specific constraints that differ from user-created blueprints. | Identifier | Display name | Purpose | Can delete | Can extend | | ------------------ | --------------------- | ------------------------------------------------ | ---------- | -------------------- | | `_user` | User | Represents users in your organization | No | Yes (relations only) | | `_team` | Team | Represents teams in your organization | No | Yes (relations only) | | `_scorecard` | Scorecard | Tracks scorecards defined in Port | No | No | | `_rule` | Scorecard Rule | Individual rules within scorecards | No | No | | `_rule_result` | Scorecard Rule Result | Evaluation results of scorecard rules | No | No | | `_ai_agent` | AI Agent | AI agents registered in your portal | No | No | | `_ai_invocations` | AI Invocation | Records of AI agent interactions | No | No | | `_ai_conversation` | AI Conversation | Groups related AI invocations into conversations | No | No | | `_mcp_server` | MCP Server | External MCP servers connected to Port | No | No | | `_workflow` | Workflow | Workflows defined in your portal | No | No | System blueprint constraints System blueprints are managed by Port and cannot be deleted or have their core schema modified. They are automatically created and maintained to support platform functionality. The `_user` and `_team` blueprints can be extended with additional relations to link Port users and teams to entities from your integrations (for example, linking `_user` to a `githubUser` or `pagerdutyUser`). You can also configure permissions on system blueprints to control user access to features (for example, restricting AI access via `_ai_invocations` permissions). In addition to system blueprints, Port uses special identifiers for navigation: * `$home`: References the portal home page in URLs and navigation contexts. --- # Set up blueprints Blueprints are the most basic building block in Port. They are used to represent assets in your organization, and the relationships between them. Blueprints are comprised of [properties](/context-lake/data-model/setup-blueprint/properties/overview/.md), which are used to define the structure of the data they represent. Port supports a wide variety of property types, allowing you to model your data in the most accurate way possible. Blueprints are completely customizable, and support any number of properties. They are created and managed in the [Data model](https://app.port.io/settings/data-model) of the portal. ## Common blueprints[​](#common-blueprints "Direct link to Common blueprints") Blueprints can be used to represent any asset in your software catalog, some common examples are: * Microservices * Packages * Package versions * CI jobs * K8s Clusters * Cloud accounts * Cloud environments * Developer environments * Service deployment * Pods * VMs Check out our [**live demo**](https://demo.port.io/settings) to see an example of a fleshed-out builder with a variety of blueprints. ## Configure blueprints in Port[​](#configure-blueprints-in-port "Direct link to Configure blueprints in Port") Port offers a variety of ways to create and edit blueprints: * Port AI * UI * API * Terraform * Pulumi You can use [Port AI](/port-ai/port-ai-assistant/.md) to create and configure blueprints through natural language - no manual JSON editing required. Open the AI assistant in your Port portal and describe what you need. For example: *"Create a microservice blueprint with properties for programming language (string), repository URL (URL), and deployment status (string enum with values: active, inactive, deprecated)"* Port AI will generate and apply the blueprint schema for you. You can then refine it with follow-up prompts like *"Add an owner team property"* or *"Make the repository URL required"*. MCP server If you have the [Port MCP server](/agent-management/port-mcp-server/overview/.md) configured in your IDE, you can manage blueprints directly from your coding environment using the same natural language approach. Create in Port ``` { "identifier": "myIdentifier", "title": "My title", "description": "My description", "icon": "My icon", "calculationProperties": {}, "schema": { "properties": {}, "required": [] }, "relations": {} } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. #### To edit an existing blueprint: 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Expand your desired blueprint by double-clicking on it. 3. Here you can add, remove, or edit this blueprint's [properties](/context-lake/data-model/setup-blueprint/properties/overview/.md) and [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md). 4. To edit the blueprint's metadata (title, icon, etc.), click on the `...` icon in the top right corner of the blueprint card, and choose `Edit blueprint`. #### To create a new blueprint: 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on `+ Blueprint`: 3. Choose a title, icon, and description for your blueprint, then click `Create`. 4. Your blueprint has been created, the next step is to [ingest data into it](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md). ``` resource "port_blueprint" "myBlueprint" { title = "My blueprint" icon = "My icon" identifier = "myIdentifier" description = "My description" properties { string_props = { "myProperty" = { type = "string" title = "My Property" } "myUrlProperty" = { title = "URL Property" format = "url" } } } } ``` Full example For a full example, check the [Terraform-Managed Blueprint Example](/context-lake/data-model/iac/terraform-managed-blueprint/.md) page. * Python * TypeScript * JavaScript * GO ``` """A Python Pulumi program""" impor t pulumi from port_pulumi import Blueprint blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", icon="My icon", description="My description", properties=port.BlueprintPropertiesArgs( string_props={ "myStringProp": port.BlueprintPropertiesStringPropsArgs( title="My string", required=False ), "myUrlProp": port.BlueprintPropertiesStringPropsArgs( title="My url", required=False, format="url" ), "myEmailProp": port.BlueprintPropertiesStringPropsArgs( title="My email", required=False, format="email" ), "myUserProp": port.BlueprintPropertiesStringPropsArgs( title="My user", required=False, format="user" ), "myTeamProp": port.BlueprintPropertiesStringPropsArgs( title="My team", required=False, format="team" ), "myDatetimeProp": port.BlueprintPropertiesStringPropsArgs( title="My datetime", required=False, format="date-time" ), "myTimerProp": port.BlueprintPropertiesStringPropsArgs( title="My timer", required=False, format="timer" ), "myYAMLProp": port.BlueprintPropertiesStringPropsArgs( title="My yaml", required=False, format="yaml" ), } ) ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", icon: "My icon", description: "My description", properties: [ { identifier: "language", title: "Language", type: "string", required: true, }, ], }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", icon: "My icon", description: "My description", properties: [ { name: "language", value: "Node", }, ], }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Icon: pulumi.String("My icon"), Description: pulumi.String("My description"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myStringProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My string"), Required: pulumi.Bool(false), }, }, }, }) ctx.Export("blueprint", blueprint.Title) if err != nil { return err } return nil }) } ``` Full example For a full example, check the [Pulumi-Managed Blueprint Example](/context-lake/data-model/iac/pulumi-managed-blueprint/.md) page. ## Blueprint structure[​](#blueprint-structure "Direct link to Blueprint structure") Each blueprint is represented by a [Json schema](https://json-schema.org/), as shown in the following section: Create in Port ``` { "identifier": "myIdentifier", "title": "My title", "description": "My description", "icon": "My icon", "calculationProperties": {}, "schema": { "properties": { "myProp1": { "type": "my_type", "title": "My title" }, "myProp2": { "type": "my_special_type", "title": "My special title" } }, "required": [] }, "relations": {} } ``` ### Structure table[​](#structure-table "Direct link to Structure table") | Field | Description | Notes | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `identifier` | The identifier is used for API calls, programmatic access and distinguishing between different blueprints. | **Required**. (Maximum 100 characters) | | `title` | Human-readable name for the blueprint. | **Required**. (Maximum 100 characters) | | `description` | The value is visible as a tooltip to users when hovering over the info icon in the UI. | | | `icon` | Icon for the blueprint and entities of the blueprint. | See the full icon list [below](#full-icon-list). | | `calculationProperties` | Contains the properties defined using [calculation properties](/context-lake/data-model/setup-blueprint/properties/calculation-property/.md). | **Required** | | `mirrorProperties` | Contains the properties defined using [mirror properties](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md). | | | `schema` | An object containing two nested fields: `properties` and `required`. | **Required**. See the schema structure [here](#schema-object). | | `ownership` | Defines how ownership of entities is determined. | See the [ownership](/context-lake/business-context/ownership/.md#the-ownership-property) section for details. | Available properties All available properties are listed in the [properties](/context-lake/data-model/setup-blueprint/properties/overview/.md) page ### Schema object[​](#schema-object "Direct link to Schema object") ``` "schema": { "properties": {}, "required": [] } ``` | Schema field | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------- | | `properties` | See the [`properties`](/context-lake/data-model/setup-blueprint/properties/overview/.md) section for more details. | | `required` | A list of the **required** properties, out of the `properties` object list.
These are mandatory fields to fill in the UI form. | ### Full icon list[​](#full-icon-list "Direct link to Full icon list") Port provides a wide selection of built-in icons for use in blueprints, properties, actions, action inputs, etc. Enterprise customers can also [upload custom icons](/interface-builder/port-interface/customization/.md#custom-icons) to align with their organization's branding. **Full icon list (click to expand)** `Actions, Add, AddUser, AI, Airflow, Alert, AmazonEC2, AmazonEKS, AmazonRDS, AmazonS3, Amplication, Ansible, API, APIalt, ApiDocs, APIEndpoint, AppRunner, Apps, Aqua, Argo, ArgoRollouts, Armis, AuditLog, Aws, Azure, AzureAD, AzureDevops, AzurePipline, AzureProvider, Backstage, BadgeAlert, Bar, BitBucket, BitBucketGitops, BlankPage, Blueprint, Bolt, Book, Box, Branch, Bucket, Buddy, Bug, Bulb, Calendar, Catalog, Chat, Checklist, Checkmarx, CICD, CircleCI, Clickup, Clock, ClockLoader, ClosedFolder, Cloud, Cloudflare, CloudFormation, Cluster, Codacy, Code, CodeBlocks, Codecov, Codefresh, Columns, Commit, ConcourseCI, Confluence, Consul, Cookiecutter, Coralogix, Cost, CPlusPlus, CPU, Crossplane, CSharp, Css3, CSV, Customer, Database, Databricks, Datadog, Day2Operation, DBT, DefaultBlueprint, DefaultProperty, Delete, Dependabot, DeployedAt, Deployment, Deployments, Details, DevopsTool, DevTV, Docker, Docs, DownArrow, Download, DrawIO, Drive, DynamicFeed, Dynatrace, Ecs, Edit, ElasticSearch, Emergency, EmptyBox, Environment, Environments, ErrorLogs, EU, Euro, Event, Exporter, Falcosidekick, Figma, Finance, FireHydrant, FitView, Flag, Flagsmith, Fluentd, Fluxcd, Flyway, GCE, General, Git, GitHub, GithubActions, GithubCopilot, GithubGitops, GitIssue, GitLab, GitLabGitops, GitPullRequest, GitSubIcon, GitVersion, GKE, Go, Google, GoogleCloud, GoogleCloudPlatform, GoogleMaps, GPU, Grafana, Graphql, groundcover, GroupBy, HAProxy, HashiCorp, Health, Helm, Help, Home, HourGlass, HourGlassExpired, Html5, Hubspot, Humio, IaC, Infinity, Instance, Istio, Java, Javascript, Jenkins, JFrog, JfrogXray, Jira, Job, Js, JsonEditor, JumpCloudProvider, Jupyter, Kafka, KafkaUI, Key, Kiali, Kibana, KNative, Komodor, Kotlin, Kubecost, Kyverno, Lambda, Lastpass, Launchdarkly, Layers, Learn, LeftArrow, Linear, LineChart, Link, LinkOut, Linux, List, Lock, LogOut, Logz, LucidCharts, Mail, Manager, ManageSearch, Markdown, Matlab, Merge, Metric, Microservice, Microservices, Microsoft, MicrosoftEntraID, Migrate, Miro, Misconfiguration, Mixpanel, MongoDb, Moon, Move, Neo4j, NewPage, NewRelic, Node, NodeJS, Notion, NSQ, Ocean, OctopusDeploy, Okta, OneLogin, Opencost, OpenFolder, OpsGenie, Organization, OutSystems, Package, Pagerduty, Perl, Permissions, Php, Pie, Pipeline, Plug, PlugOff, Port, PostgreSQL, Prometheus, Properties, Pulsar, Pulumi, Python, R, Rancher, React, RedhatOpenshift, Redis, Register, Relation, Reminder, Repeat, Reset, RightArrow, Rocket, Role, Rope, Ruby, SalesForce, Scala, Score, SDK, Search, SendGrid, Sensu, Sentry, Server, Service, Servicenow, Settings, SettingsEthernet, Share, Shield, Sinch, SingleStore, Siren, Slack, Snowflake, SNS, Snyk, SonarCloud, Sonarqube, SonartypeNexusIQ, Sort, Speed, Spinnaker, Spring, SQL, Stack, Stackhawk, StackOverflow, Star, StatusPage, Stonebranch, Stoplight, Store, Stripe, Sun, Supervisor, Support, Swagger, Swift, Sync, Table, Tableau, Team, TeamCity, Template, Terraform, Thanos, Travel, Tribe, Trivy, Twilio, TwoUsers, Typescript, Unavailable, Unleash, Unlock, UpArrow, Updates, Url, USA, User, Users, Version, Vulnerability, Webhook, Widget, Windows, Wiz, Youtrack, Zendesk, Zipkin` --- # Aggregation property Aggregation properties allow you to calculate metrics based on the [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md) in your catalog. Using the aggregation property enables you to see relevant metrics on related entities, without having to manually calculate them. Aggregations can be performed on any blueprint that is related in any way to the current blueprint (directly, indirectly, upstream or downstream). Relation traversal By default, aggregations consider all possible paths between the source and target entities. To control which path is used, see [path filter](#path-filter). ## Common usage[​](#common-usage "Direct link to Common usage") Use aggregation properties to calculate metrics over related entities, such as: * The number of open Jira issues for a service. * The number of unresolved high and critical vulnerabilities. * The average number of deployments per week. * The total monthly cost of related cloud resources. You can use these metrics in scorecards and initiative rules. ## When to use aggregation properties[​](#when-to-use-aggregation-properties "Direct link to When to use aggregation properties") Aggregation property ideally will be defined on blueprints which are in the **higher abstraction level** in your catalog. Those blueprints are usually the ones that are **related to many other blueprints** in your catalog, and therefore, they are the ones that can benefit the most from aggregation properties. ## Specification[​](#specification "Direct link to Specification") The `aggregationProperties` key is a top-level key in a blueprint definition, alongside `identifier`, `title`, and `schema`. The aggregation property supports calculations by entities or by property. * Calculations by **entities** are performed on the entities that match the query (e.g. count the number of entities that match the query). * Calculations by **property** are performed on the property of the entities that match the query (e.g. sum the value of the property of the entities that match the query). ## Definitions[​](#definitions "Direct link to Definitions") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * API * Terraform * Pulumi ### Calculate by entities Calculate by entities is used to calculate metrics based on the entities that match the query. Supported methods: * `count` - Count the number of entities that match the query. For example, count the number of open Jira issues related to a microservice. * `average` - Calculate the average of entities per a defined time period. For example, calculate the average deployment frequency per week. - Entities Count - Entities Average In this example, we have a microservice blueprint and we want to calculate the number of open Jira issues related to each microservice. ``` { "aggregationProperties": { "numberOfOpenJiraIssues": { "title": "Number of open Jira issues", "target": "jiraIssue", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "OPEN" } ] } } } } ``` The `aggregationProperties` contains a key called `numberOfOpenJiraIssues` which is the identifier of the aggregation property we want to define. * `title` - The title of the aggregation property. * `target` - The blueprint we want to aggregate data from. * `query` - **Optional** - The query that will be performed on the target blueprint. The query is based on the Filters to include or exclude specific data based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) * `calculationSpec` - The calculation specification. * `"calculationBy": "entities"` - The calculation will be performed on the **entities** that match the query. * `"func": "count"` - is the function we want to use for the calculation. In this example, we have a microservice blueprint, and we want to calculate the average deployment frequency for each of the microservices. ``` { "aggregationProperties": { "averageDeploymentFrequency": { "title": "Average deployment frequency", "target": "deployment", "calculationSpec": { "calculationBy": "entities", "func": "average", "averageOf": "week", "measureTimeBy": "$createdAt" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "SUCCESS" } ] } } } } ``` The `aggregationProperties` contains a key called `averageDeploymentFrequency` which is the identifier of the aggregation property we want to define. * `title` - The title of the aggregation property. * `target` - The blueprint we want to aggregate data from. * `query` - **Optional** - The query that will be performed on the target blueprint. The query is based on the Filters to include or exclude specific data based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) * `calculationSpec` - The calculation specification. * `"calculationBy": "entities"` - The calculation will be performed on the entities that match the query (e.g. count the number of entities that match the query). * `"func": "average"` - is the function we want to use for the calculation. * `"averageOf": "week"` - The time period we want to calculate the average for. In this example, we want to calculate the average deployment frequency for each week. Supported Options are: `hour, day ,week and month` * `"measureTimeBy": "$createdAt"` - The time property we want to measure the average by. You can use any date property in the target blueprint by default, we add $createdAt and $updatedAt as [meta-properties](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md) to each entity. ### Calculate by property Calculate by property is used to calculate metrics based on the property of the entities that match the query. The property type must be a number. Supported methods: * `sum` - Sum the value of the property of the entities that match the query. For example, sum the story points of open Jira issues related to a microservice. * `average` - Calculate the average of the property of the entities that match the query. For example, average cpu usage of a microservice. * `min` - Calculate the minimum value of the property of the entities that match the query. For example, lowest alert severity of a microservice in the last week. * `max` - Calculate the maximum value of the property of the entities that match the query. For example, highest alert severity of a microservice in the last week. * `median` - Calculate the median value of the property of the entities that match the query. For example, median cpu usage of a microservice in the last week. - Sum - Average - Min - Max - Median In this example, we have a microservice blueprint, and we want to calculate the sum of the story points of open Jira issues related to each microservice. ``` { "aggregationProperties": { "sumOfStoryPoints": { "title": "Sum of story points", "target": "jiraIssue", "calculationSpec": { "calculationBy": "property", "func": "sum", "property": "storyPoints" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "OPEN" } ] } } } } ``` The `aggregationProperties` contains a key called `sumOfStoryPoints` which is the identifier of the aggregation property we want to define. * `title` - The title of the aggregation property. * `target` - The blueprint we want to aggregate data from. * `query` - **Optional** - The query that will be performed on the target blueprint. The query is based on the Filters to include or exclude specific data based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) * `calculationSpec` - The calculation specification. * `"calculationBy": "property"` - The calculation will be performed on the property of the entities that match the query (e.g. sum the value of the property of the entities that match the query). * `"func": "sum"` is the function we want to use for the calculation. * `"property": "storyPoints"` - The property we want to calculate the sum of. The property type must be a number. In this example, we have a microservice blueprint, and we want to calculate the average cpu usage of each microservice. ``` { "aggregationProperties": { "averageCpuUsage": { "title": "Average CPU usage", "target": "deployment", "calculationSpec": { "calculationBy": "property", "func": "average", "property": "cpuUsage", "averageOf": "week", "measureTimeBy": "$createdAt" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "SUCCESS" } ] } } } } ``` The `aggregationProperties` contains a key called `averageCpuUsage` which is the identifier of the aggregation property we want to define. * `title` - The title of the aggregation property. * `target` - The blueprint we want to aggregate data from. * `query` - **Optional** - The query that will be performed on the target blueprint. The query is based on the Filters to include or exclude specific data based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) * `calculationSpec` - The calculation specification. * `calculationBy` - `property` The calculation will be performed on the property of the entities that match the query (e.g. average the value of the property of the entities that match the query). * `"func": "average"` - is the function we want to use for the calculation. * `"property": "cpuUsage"` - The property we want to calculate the average of. The property type must be a number. * `"averageOf": "week"` - The time period we want to calculate the average for. In this example, we want to calculate the average deployment frequency for each week. Supported Options are: \`hour, day ,week, month and total * `"measureTimeBy": "$createdAt"` - The time property we want to measure the average by. You can use any date property in the target blueprint by default, we add $createdAt and $updatedAt as [meta-properties](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md) to each entity. In this example, we have a microservice blueprint, and we want to calculate the minimum value of the alert severity of each microservice in the last week. ``` { "aggregationProperties": { "minAlertSeverity": { "title": "Minimum alert severity", "target": "alert", "calculationSpec": { "calculationBy": "property", "func": "min", "property": "severity" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "OPEN" }, { "property": "$createdAt", "operator": "between", "value": { "preset": "lastWeek" } } ] } } } } ``` The `aggregationProperties` contains a key called `minAlertSeverity` which is the identifier of the aggregation property we want to define. * `title` - The title of the aggregation property. * `target` - The blueprint we want to aggregate data from. * `query` - **Optional** - The query that will be performed on the target blueprint. The query is based on the Filters to include or exclude specific data based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) * `calculationSpec` - The calculation specification. * `"calculationBy": "property"` The calculation will be performed on the property of the entities that match the query (e.g. minimum value of the property of the entities that match the query). * `"func": "min"` - is the function we want to use for the calculation. * `"property": "severity"` - The property we want to calculate the minimum of. The property type must be a number. In this example, we have a microservice blueprint, and we want to calculate the maximum value of the alert severity of each microservice in the last week. ``` { "aggregationProperties": { "maxAlertSeverity": { "title": "Maximum alert severity", "target": "alert", "calculationSpec": { "calculationBy": "property", "func": "max", "property": "severity" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "OPEN" }, { "property": "$createdAt", "operator": "between", "value": { "preset": "lastWeek" } } ] } } } } ``` The `aggregationProperties` contains a key called `maxAlertSeverity` which is the identifier of the aggregation property we want to define. * `title` - The title of the aggregation property. * `target` - The blueprint we want to aggregate data from. * `query` - **Optional** - The query that will be performed on the target blueprint. The query is based on the Filters to include or exclude specific data based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) * `calculationSpec` - The calculation specification. * `"calculationBy": "property"` The calculation will be performed on the property of the entities that match the query (e.g. maximum value of the property of the entities that match the query). * `"func": "max"` - is the function we want to use for the calculation. * `"property": "severity"` - The property we want to calculate the maximum of. The property type must be a number. In this example, we have a microservice blueprint, and we want to calculate the median value of the cpu usage across all related pods of each microservice. ``` { "aggregationProperties": { "medianCpuUsage": { "title": "Median CPU usage", "target": "pod", "calculationSpec": { "calculationBy": "property", "func": "median", "property": "cpuUsage" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "OPEN" }, { "property": "$createdAt", "operator": "between", "value": { "preset": "lastWeek" } } ] } } } } ``` The `aggregationProperties` contains a key called `medianCpuUsage` which is the identifier of the aggregation property we want to define. * `title` - The title of the aggregation property. * `target` - The blueprint we want to aggregate data from. * `query` - **Optional** - The query that will be performed on the target blueprint. The query is based on the Filters to include or exclude specific data based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) * `calculationSpec` - The calculation specification. * `"calculationBy": "property"` The calculation will be performed on the property of the entities that match the query (e.g. median value of the property of the entities that match the query). * `"func": "median"` is the function we want to use for the calculation. * `"property": "cpuUsage"` - The property we want to calculate the median of. The property type must be a number. ### Calculate by entities Calculate by entities is used to calculate metrics based on the entities that match the query. Supported methods: * `count_entities` - Count the number of entities that match the query. For example, count the number of open Jira issues related to a microservice. * `average_entities` - Calculate the average of entities per a defined time period. For example, calculate the average deployment frequency per week. - Entities Count - Entities Average In this example, we create a parent blueprint with a child blueprint and an aggregation property to count the parent kids: ``` resource "port_blueprint" "parent_blueprint" { title = "Parent Blueprint" icon = "Terraform" identifier = "parent" description = "" properties = { number_props = { "age" = { title = "Age" } } } } resource "port_blueprint" "child_blueprint" { title = "Child Blueprint" icon = "Terraform" identifier = "child" description = "" properties = { number_props = { "age" = { title = "Age" } } } relations = { "parent" = { title = "Parent" target = port_blueprint.parent_blueprint.identifier } } } resource "port_aggregation_properties" "parent_aggregation_properties" { blueprint_identifier = port_blueprint.parent_blueprint.identifier properties = { count_kids = { target_blueprint_identifier = port_blueprint.child_blueprint.identifier title = "Count Kids" icon = "Terraform" description = "Count Kids" method = { count_entities = true } } } } ``` In this example, we have a microservice blueprint, and we want to calculate the average deployment frequency for each of the microservices. ``` resource "port_blueprint" "microservice_blueprint" { title = "Microservice Blueprint" icon = "Terraform" identifier = "microservice" description = "" } resource "port_blueprint" "deployment_blueprint" { title = "Deployment Blueprint" icon = "Terraform" identifier = "deployment" description = "" relations = { "microservice" = { title = "Microservice" target = port_blueprint.microservice_blueprint.identifier } } } resource "port_aggregation_properties" "microservice_aggregation_properties" { blueprint_identifier = port_blueprint.microservice_blueprint.identifier properties = { average_deployment_frequency = { target_blueprint_identifier = port_blueprint.deployment_blueprint.identifier title = "Average deployment frequency" icon = "Terraform" description = "Average deployment frequency" method = { average_entities = { average_of = "week" measure_time_by = "$createdAt" } } } } } ``` ### Calculate by property Calculate by property is used to calculate metrics based on the property of the entities that match the query. The property type must be a number. Supported methods: * `average_by_property` - Calculate the average of the property of the entities that match the query. For example, average cpu usage of a microservice. * `aggregate_by_property` - Aggregate the value of the property of the entities that match the query, supported aggregation functions are: `sum, min, max, median`. For example, sum the story points of open Jira issues related to a microservice. - Sum - Average - Min - Max - Median In this example, we have a microservice blueprint, and we want to calculate the sum of the story points of open Jira issues related to each microservice. ``` resource "port_blueprint" "microservice_blueprint" { title = "Microservice Blueprint" icon = "Terraform" identifier = "microservice" description = "" } resource "port_blueprint" "jira_issue_blueprint" { title = "Jira Issue Blueprint" icon = "Terraform" identifier = "jira_issue" description = "" properties = { number_props = { "storyPoints" = { title = "Story Points" } } } relations = { "microservice" = { title = "Microservice" target = port_blueprint.microservice_blueprint.identifier } } } resource "port_aggregation_properties" "microservice_aggregation_properties" { blueprint_identifier = port_blueprint.microservice_blueprint.identifier properties = { sum_of_story_points = { target_blueprint_identifier = port_blueprint.jira_issue_blueprint.identifier title = "Sum of story points" icon = "Terraform" description = "Sum of story points" method = { aggregate_by_property = { func = "sum" property = "storyPoints" } } } } } ``` In this example, we have a microservice blueprint, and we want to calculate the average cpu usage of each microservice. ``` resource "port_blueprint" "microservice_blueprint" { title = "Microservice Blueprint" icon = "Terraform" identifier = "microservice" description = "" } resource "port_blueprint" "deployment_blueprint" { title = "Deployment Blueprint" icon = "Terraform" identifier = "deployment" description = "" properties = { number_props = { "cpuUsage" = { title = "CPU Usage" } } } relations = { "microservice" = { title = "Microservice" target = port_blueprint.microservice_blueprint.identifier } } } resource "port_aggregation_properties" "microservice_aggregation_properties" { blueprint_identifier = port_blueprint.microservice_blueprint.identifier properties = { average_cpu_usage = { target_blueprint_identifier = port_blueprint.deployment_blueprint.identifier title = "Average CPU usage" icon = "Terraform" description = "Average CPU usage" method = { average_by_property = { average_of = "week" measure_time_by = "$createdAt" property = "cpuUsage" } } } } } ``` In this example, we have a microservice blueprint, and we want to calculate the minimum value of the alert severity of each microservice in the last week. ``` resource "port_blueprint" "microservice_blueprint" { title = "Microservice Blueprint" icon = "Terraform" identifier = "microservice" description = "" } resource "port_blueprint" "alert_blueprint" { title = "Alert Blueprint" icon = "Terraform" identifier = "alert" description = "" properties = { number_props = { "severity" = { title = "Severity" } } } relations = { "microservice" = { title = "Microservice" target = port_blueprint.microservice_blueprint.identifier } } } resource "port_aggregation_properties" "microservice_aggregation_properties" { blueprint_identifier = port_blueprint.microservice_blueprint.identifier properties = { min_alert_severity = { target_blueprint_identifier = port_blueprint.alert_blueprint.identifier title = "Minimum alert severity" icon = "Terraform" description = "Minimum alert severity" method = { aggregate_by_property = { func = "min" property = "severity" } } } } } ``` In this example, we have a microservice blueprint, and we want to calculate the maximum value of the alert severity of each microservice in the last week. ``` resource "port_blueprint" "microservice_blueprint" { title = "Microservice Blueprint" icon = "Terraform" identifier = "microservice" description = "" } resource "port_blueprint" "alert_blueprint" { title = "Alert Blueprint" icon = "Terraform" identifier = "alert" description = "" properties = { number_props = { "severity" = { title = "Severity" } } } relations = { "microservice" = { title = "Microservice" target = port_blueprint.microservice_blueprint.identifier } } } resource "port_aggregation_properties" "microservice_aggregation_properties" { blueprint_identifier = port_blueprint.microservice_blueprint.identifier properties = { max_alert_severity = { target_blueprint_identifier = port_blueprint.alert_blueprint.identifier title = "Maximum alert severity" icon = "Terraform" description = "Maximum alert severity" method = { aggregate_by_property = { func = "max" property = "severity" } } } } } ``` In this example, we have a microservice blueprint, and we want to calculate the median value of the cpu usage across all related pods of each microservice. ``` resource "port_blueprint" "microservice_blueprint" { title = "Microservice Blueprint" icon = "Terraform" identifier = "microservice" description = "" } resource "port_blueprint" "pod_blueprint" { title = "Pod Blueprint" icon = "Terraform" identifier = "pod" description = "" properties = { number_props = { "cpuUsage" = { title = "CPU Usage" } } } relations = { "microservice" = { title = "Microservice" target = port_blueprint.microservice_blueprint.identifier } } } resource "port_aggregation_properties" "microservice_aggregation_properties" { blueprint_identifier = port_blueprint.microservice_blueprint.identifier properties = { median_cpu_usage = { target_blueprint_identifier = port_blueprint.pod_blueprint.identifier title = "Median CPU usage" icon = "Terraform" description = "Median CPU usage" method = { aggregate_by_property = { func = "median" property = "cpuUsage" } } } } } ``` ### Using a `query` in aggregation properties You can use the query to filter the entities you want to perform the calculation on. The query is based on the Filters to include or exclude specific data based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) #### Query Example Create a repository blueprint and a pull request blueprint and an aggregation property to calculate the average of fix pull request per month: To do that we will add a query to the aggregation property to filter only pull requests with fixed title: ``` resource "port_blueprint" "repository_blueprint" { title = "Repository Blueprint" icon = "Terraform" identifier = "repository" description = "" } resource "port_blueprint" "pull_request_blueprint" { title = "Pull Request Blueprint" icon = "Terraform" identifier = "pull_request" description = "" properties = { string_props = { "status" = { title = "Status" } } } relations = { "repository" = { title = "Repository" target = port_blueprint.repository_blueprint.identifier } } } resource "port_aggregation_properties" "repository_aggregation_properties" { blueprint_identifier = port_blueprint.repository_blueprint.identifier properties = { fix_pull_requests_count = { target_blueprint_identifier = port_blueprint.pull_request_blueprint.identifier title = "Pull requests per month" icon = "Terraform" description = "Pull requests per month" method = { average_entities = { average_of = "month" measure_time_by = "$createdAt" } } query = jsonencode( { "combinator" : "and", "rules" : [ { "property" : "$title", "operator" : "ContainsAny", "value" : ["fix", "fixed", "fixing", "Fix"] } ] } ) } } } ``` ### Calculate by entities Calculate by entities is used to calculate metrics based on the entities that match the query. Supported methods: * `countEntities` - Count the number of entities that match the query. For example, count the number of open Jira issues related to a microservice. * `averageEntities` - Calculate the average of entities per a defined time period. For example, calculate the average deployment frequency per week. In Pulumi, aggregation properties are defined using the `port.AggregationProperties` resource (separate from the blueprint). Below are examples in different languages. * TypeScript * Python * JavaScript * Go **Entities count** - Count the number of child entities related to each parent: ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; const parentBlueprint = new port.Blueprint("parentBlueprint", { identifier: "parent", title: "Parent Blueprint", properties: { numberProps: { age: { title: "Age" }, }, }, }); const childBlueprint = new port.Blueprint("childBlueprint", { identifier: "child", title: "Child Blueprint", properties: { numberProps: { age: { title: "Age" }, }, }, relations: { parent: { title: "Parent", target: parentBlueprint.identifier, }, }, }); const parentAggregationProperties = new port.AggregationProperties("parentAggregationProperties", { blueprintIdentifier: parentBlueprint.identifier, properties: { count_kids: { targetBlueprintIdentifier: childBlueprint.identifier, title: "Count Kids", icon: "Terraform", description: "Count Kids", method: { countEntities: true, }, }, }, }); ``` **Entities average** - Average deployment frequency per week for each microservice: ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; const microserviceBlueprint = new port.Blueprint("microserviceBlueprint", { identifier: "microservice", title: "Microservice Blueprint", }); const deploymentBlueprint = new port.Blueprint("deploymentBlueprint", { identifier: "deployment", title: "Deployment Blueprint", relations: { microservice: { title: "Microservice", target: microserviceBlueprint.identifier, }, }, }); const microserviceAggregationProperties = new port.AggregationProperties("microserviceAggregationProperties", { blueprintIdentifier: microserviceBlueprint.identifier, properties: { average_deployment_frequency: { targetBlueprintIdentifier: deploymentBlueprint.identifier, title: "Average deployment frequency", icon: "Terraform", description: "Average deployment frequency", method: { averageEntities: { averageOf: "week", measureTimeBy: "$createdAt", }, }, }, }, }); ``` **Entities count** - Count the number of child entities related to each parent: ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint, AggregationProperties parent_blueprint = Blueprint( "parentBlueprint", identifier="parent", title="Parent Blueprint", properties={ "number_props": { "age": {"title": "Age"}, }, }, ) child_blueprint = Blueprint( "childBlueprint", identifier="child", title="Child Blueprint", properties={ "number_props": { "age": {"title": "Age"}, }, }, relations={ "parent": { "title": "Parent", "target": parent_blueprint.identifier, }, }, ) parent_aggregation_properties = AggregationProperties( "parentAggregationProperties", blueprint_identifier=parent_blueprint.identifier, properties={ "count_kids": { "target_blueprint_identifier": child_blueprint.identifier, "title": "Count Kids", "icon": "Terraform", "description": "Count Kids", "method": { "count_entities": True, }, }, }, ) ``` **Entities average** - Average deployment frequency per week for each microservice: ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint, AggregationProperties microservice_blueprint = Blueprint( "microserviceBlueprint", identifier="microservice", title="Microservice Blueprint", ) deployment_blueprint = Blueprint( "deploymentBlueprint", identifier="deployment", title="Deployment Blueprint", relations={ "microservice": { "title": "Microservice", "target": microservice_blueprint.identifier, }, }, ) microservice_aggregation_properties = AggregationProperties( "microserviceAggregationProperties", blueprint_identifier=microservice_blueprint.identifier, properties={ "average_deployment_frequency": { "target_blueprint_identifier": deployment_blueprint.identifier, "title": "Average deployment frequency", "icon": "Terraform", "description": "Average deployment frequency", "method": { "average_entities": { "average_of": "week", "measure_time_by": "$createdAt", }, }, }, }, ) ``` **Entities count** - Count the number of child entities related to each parent: ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const parentBlueprint = new port.Blueprint("parentBlueprint", { identifier: "parent", title: "Parent Blueprint", properties: { numberProps: { age: { title: "Age" }, }, }, }); const childBlueprint = new port.Blueprint("childBlueprint", { identifier: "child", title: "Child Blueprint", properties: { numberProps: { age: { title: "Age" }, }, }, relations: { parent: { title: "Parent", target: parentBlueprint.identifier, }, }, }); const parentAggregationProperties = new port.AggregationProperties("parentAggregationProperties", { blueprintIdentifier: parentBlueprint.identifier, properties: { count_kids: { targetBlueprintIdentifier: childBlueprint.identifier, title: "Count Kids", icon: "Terraform", description: "Count Kids", method: { countEntities: true, }, }, }, }); ``` **Entities average** - Average deployment frequency per week for each microservice: ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const microserviceBlueprint = new port.Blueprint("microserviceBlueprint", { identifier: "microservice", title: "Microservice Blueprint", }); const deploymentBlueprint = new port.Blueprint("deploymentBlueprint", { identifier: "deployment", title: "Deployment Blueprint", relations: { microservice: { title: "Microservice", target: microserviceBlueprint.identifier, }, }, }); const microserviceAggregationProperties = new port.AggregationProperties("microserviceAggregationProperties", { blueprintIdentifier: microserviceBlueprint.identifier, properties: { average_deployment_frequency: { targetBlueprintIdentifier: deploymentBlueprint.identifier, title: "Average deployment frequency", icon: "Terraform", description: "Average deployment frequency", method: { averageEntities: { averageOf: "week", measureTimeBy: "$createdAt", }, }, }, }, }); ``` **Entities count** - Count the number of child entities related to each parent: ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { parentBlueprint, err := port.NewBlueprint(ctx, "parentBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("parent"), Title: pulumi.String("Parent Blueprint"), Properties: port.BlueprintPropertiesArgs{ NumberProps: port.BlueprintPropertiesNumberPropsMap{ "age": port.BlueprintPropertiesNumberPropsArgs{ Title: pulumi.String("Age"), }, }, }, }) if err != nil { return err } childBlueprint, err := port.NewBlueprint(ctx, "childBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("child"), Title: pulumi.String("Child Blueprint"), Properties: port.BlueprintPropertiesArgs{ NumberProps: port.BlueprintPropertiesNumberPropsMap{ "age": port.BlueprintPropertiesNumberPropsArgs{ Title: pulumi.String("Age"), }, }, }, Relations: port.BlueprintRelationsMap{ "parent": port.BlueprintRelationsArgs{ Title: pulumi.String("Parent"), Target: parentBlueprint.Identifier, }, }, }) if err != nil { return err } _, err = port.NewAggregationProperties(ctx, "parentAggregationProperties", &port.AggregationPropertiesArgs{ BlueprintIdentifier: parentBlueprint.Identifier, Properties: port.AggregationPropertiesPropertiesMap{ "count_kids": port.AggregationPropertiesPropertiesArgs{ TargetBlueprintIdentifier: childBlueprint.Identifier, Title: pulumi.String("Count Kids"), Icon: pulumi.String("Terraform"), Description: pulumi.String("Count Kids"), Method: port.AggregationPropertiesPropertiesMethodArgs{ CountEntities: pulumi.Bool(true), }, }, }, }) return err }) } ``` **Entities average** - Average deployment frequency per week for each microservice: ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { microserviceBlueprint, err := port.NewBlueprint(ctx, "microserviceBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("microservice"), Title: pulumi.String("Microservice Blueprint"), }) if err != nil { return err } deploymentBlueprint, err := port.NewBlueprint(ctx, "deploymentBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("deployment"), Title: pulumi.String("Deployment Blueprint"), Relations: port.BlueprintRelationsMap{ "microservice": port.BlueprintRelationsArgs{ Title: pulumi.String("Microservice"), Target: microserviceBlueprint.Identifier, }, }, }) if err != nil { return err } _, err = port.NewAggregationProperties(ctx, "microserviceAggregationProperties", &port.AggregationPropertiesArgs{ BlueprintIdentifier: microserviceBlueprint.Identifier, Properties: port.AggregationPropertiesPropertiesMap{ "average_deployment_frequency": port.AggregationPropertiesPropertiesArgs{ TargetBlueprintIdentifier: deploymentBlueprint.Identifier, Title: pulumi.String("Average deployment frequency"), Icon: pulumi.String("Terraform"), Description: pulumi.String("Average deployment frequency"), Method: port.AggregationPropertiesPropertiesMethodArgs{ AverageEntities: &port.AggregationPropertiesPropertiesMethodAverageEntitiesArgs{ AverageOf: pulumi.String("week"), MeasureTimeBy: pulumi.String("$createdAt"), }, }, }, }, }) return err }) } ``` ### Calculate by property Calculate by property is used to calculate metrics based on the property of the entities that match the query. The property type must be a number. Use `aggregateByProperty` with `func` set to `"sum"`, `"min"`, `"max"`, or `"median"`, or use `averageByProperty` for time-based averages. **Sum example (TypeScript)** - Sum of story points of open Jira issues per microservice: ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; const microserviceBlueprint = new port.Blueprint("microserviceBlueprint", { identifier: "microservice", title: "Microservice Blueprint", }); const jiraIssueBlueprint = new port.Blueprint("jiraIssueBlueprint", { identifier: "jira_issue", title: "Jira Issue Blueprint", properties: { numberProps: { storyPoints: { title: "Story Points" }, }, }, relations: { microservice: { title: "Microservice", target: microserviceBlueprint.identifier, }, }, }); const microserviceAggregationProperties = new port.AggregationProperties("microserviceAggregationProperties", { blueprintIdentifier: microserviceBlueprint.identifier, properties: { sum_of_story_points: { targetBlueprintIdentifier: jiraIssueBlueprint.identifier, title: "Sum of story points", icon: "Terraform", description: "Sum of story points", method: { aggregateByProperty: { func: "sum", property: "storyPoints", }, }, }, }, }); ``` ### Using a `query` in aggregation properties You can use the `query` property to filter the entities you want to perform the calculation on. The query is based on Port's [Search Rules](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules). **Query example (TypeScript)** - Average of fix pull requests per month per repository: ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; const repositoryBlueprint = new port.Blueprint("repositoryBlueprint", { identifier: "repository", title: "Repository Blueprint", }); const pullRequestBlueprint = new port.Blueprint("pullRequestBlueprint", { identifier: "pull_request", title: "Pull Request Blueprint", properties: { stringProps: { status: { title: "Status" }, }, }, relations: { repository: { title: "Repository", target: repositoryBlueprint.identifier, }, }, }); const repositoryAggregationProperties = new port.AggregationProperties("repositoryAggregationProperties", { blueprintIdentifier: repositoryBlueprint.identifier, properties: { fix_pull_requests_count: { targetBlueprintIdentifier: pullRequestBlueprint.identifier, title: "Pull requests per month", icon: "Terraform", description: "Pull requests per month", method: { averageEntities: { averageOf: "month", measureTimeBy: "$createdAt", }, }, query: JSON.stringify({ combinator: "and", rules: [ { property: "$title", operator: "ContainsAny", value: ["fix", "fixed", "fixing", "Fix"], }, ], }), }, }, }); ``` ### Path filter[​](#path-filter "Direct link to Path filter") The `pathFilter` option lets you control which entities are included in your aggregation calculation based on their relationship path from the source entity. This is useful for complex relationship chains, where you may want to aggregate data only from entities connected through specific paths. When a `pathFilter` is defined, Port will include only entities that can be reached through the exact relationship path you specify. * **Forwards path →**
Following relations forwards, from the source of a relation to its target (e.g., from a repository to its services). * **Backwards path ←**
Following relations backwards, from the target of a relation to its source (e.g., from a service back to its repository). **Structure** | Field | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `pathFilter.path` | An array containing the full path of relation identifiers to traverse. | | `pathFilter.fromBlueprint` | *(Optional)* The blueprint to start the path traversal from. Can be the target blueprint or omitted. If omitted, traversal starts from the source blueprint. | **For forwards paths** ``` { "pathFilter": [ { "path": ["relation1", "relation2", "relation3"] } ] } ``` **For backwards paths** ``` { "pathFilter": [ { "path": ["relation1", "relation2", "relation3"], "fromBlueprint": "{{targetBlueprint}}" } ] } ``` #### Examples[​](#examples "Direct link to Examples") Suppose you have the following data model: ![Cluster blueprint related to Deployment blueprint](/img/software-catalog/blueprint/pathFilterDiagramExample.png)

* API * Terraform * Pulumi **Example 1: Standard Path Filter - forwards path** Count how many deployments are directly related to a cluster: ``` { "identifier": "cluster", "title": "Cluster", "aggregationProperties": { "deploymentCount": { "title": "Deployment Count", "target": "deployment", "pathFilter": [ { "path": ["deployments_relation"] } ], "calculationSpec": { "calculationBy": "entities", "func": "count" } } } } ``` The `pathFilter` with `"path": ["deployments_relation"]` counts deployments that are directly related to the cluster through the `deployments_relation` relation. **Example 2: Using fromBlueprint - backwards path** Count how many clusters are related to a deployment: ``` { "identifier": "deployment", "title": "Deployment", "aggregationProperties": { "clusterCount": { "title": "Cluster Count", "target": "cluster", "pathFilter": [ { "path": ["deployments_relation"], "fromBlueprint": "cluster" } ], "calculationSpec": { "calculationBy": "entities", "func": "count" } } } } ``` The `fromBlueprint: "cluster"` specifies that the path traversal should start from the cluster blueprint (the target), then follow the path backwards through `deployments_relation` to deployment. **Example 1: Standard Path Filter - forwards path** Count how many deployments are directly related to a cluster: ``` resource "port_blueprint" "cluster_blueprint" { identifier = "cluster" title = "Cluster" relations = { "deployments_relation" = { title = "Deployments" target = port_blueprint.deployment_blueprint.identifier } } } resource "port_blueprint" "deployment_blueprint" { identifier = "deployment" title = "Deployment" } resource "port_aggregation_properties" "cluster_aggregation_properties" { blueprint_identifier = port_blueprint.cluster_blueprint.identifier properties = { deployment_count = { target_blueprint_identifier = port_blueprint.deployment_blueprint.identifier title = "Deployment Count" icon = "Terraform" description = "Deployment Count" path_filter = [ { path = ["deployments_relation"] } ] method = { count_entities = true } } } } ``` The `path = ["deployments_relation"]` counts deployments that are directly related to the cluster through the `deployments_relation` relation. **Example 2: Using fromBlueprint - backwards path** Count how many clusters are related to a deployment: ``` resource "port_blueprint" "deployment_blueprint" { identifier = "deployment" title = "Deployment" } resource "port_blueprint" "cluster_blueprint" { identifier = "cluster" title = "Cluster" relations = { "deployments_relation" = { title = "Deployments" target = port_blueprint.deployment_blueprint.identifier } } } resource "port_aggregation_properties" "deployment_aggregation_properties" { blueprint_identifier = port_blueprint.deployment_blueprint.identifier properties = { cluster_count = { target_blueprint_identifier = port_blueprint.cluster_blueprint.identifier title = "Cluster Count" icon = "Terraform" description = "Cluster Count" path_filter = [ { path = ["deployments_relation"] from_blueprint = "cluster" } ] method = { count_entities = true } } } } ``` The `from_blueprint = "cluster"` specifies that the path traversal should start from the cluster blueprint (the target), then follow the path backwards through `deployments_relation` to deployment. **Example 1: Standard path filter - forwards path** Count how many deployments are directly related to a cluster: ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; const deploymentBlueprint = new port.Blueprint("deploymentBlueprint", { identifier: "deployment", title: "Deployment", }); const clusterBlueprint = new port.Blueprint("clusterBlueprint", { identifier: "cluster", title: "Cluster", relations: { deployments_relation: { title: "Deployments", target: deploymentBlueprint.identifier, }, }, }); const clusterAggregationProperties = new port.AggregationProperties("clusterAggregationProperties", { blueprintIdentifier: clusterBlueprint.identifier, properties: { deployment_count: { targetBlueprintIdentifier: deploymentBlueprint.identifier, title: "Deployment Count", icon: "Terraform", description: "Deployment Count", pathFilters: [ { paths: ["deployments_relation"], }, ], method: { countEntities: true, }, }, }, }); ``` The `pathFilters` with `paths: ["deployments_relation"]` counts deployments that are directly related to the cluster through the `deployments_relation` relation. **Example 2: Using fromBlueprint - backwards path** Count how many clusters are related to a deployment: ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; const deploymentBlueprint = new port.Blueprint("deploymentBlueprint", { identifier: "deployment", title: "Deployment", }); const clusterBlueprint = new port.Blueprint("clusterBlueprint", { identifier: "cluster", title: "Cluster", relations: { deployments_relation: { title: "Deployments", target: deploymentBlueprint.identifier, }, }, }); const deploymentAggregationProperties = new port.AggregationProperties("deploymentAggregationProperties", { blueprintIdentifier: deploymentBlueprint.identifier, properties: { cluster_count: { targetBlueprintIdentifier: clusterBlueprint.identifier, title: "Cluster Count", icon: "Terraform", description: "Cluster Count", pathFilters: [ { paths: ["deployments_relation"], fromBlueprint: "cluster", }, ], method: { countEntities: true, }, }, }, }); ``` The `fromBlueprint: "cluster"` specifies that the path traversal should start from the cluster blueprint (the target), then follow the path backwards through `deployments_relation` to deployment. Path Filter Performance Path filters can affect the performance of aggregation calculations. In general, shorter and more direct paths perform better than long or complex multi-hop paths. ## Examples[​](#examples-1 "Direct link to Examples") Below is a collection of real-world aggregation property use cases organized by category. Each includes a description of the use case and the JSON definition you can copy directly into your blueprint. ### DevOps & deployments[​](#devops--deployments "Direct link to DevOps & deployments") **Deployment frequency (weekly average) (click to expand)** Track how often deployments occur per week for each service. ``` { "deploymentFrequency": { "title": "Deployment frequency", "target": "deployment", "calculationSpec": { "calculationBy": "entities", "func": "average", "averageOf": "week", "measureTimeBy": "$createdAt" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "SUCCESS" } ] } } } ``` **Total deployments count (click to expand)** Count the total number of deployments for a service. ``` { "totalDeployments": { "title": "Total deployments", "target": "deployment", "calculationSpec": { "calculationBy": "entities", "func": "count" } } } ``` **Failed deployments in the last week (click to expand)** Count deployments that failed in the past 7 days. ``` { "failedDeploymentsLastWeek": { "title": "Failed deployments (last week)", "target": "deployment", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "FAILED" }, { "property": "$createdAt", "operator": "between", "value": { "preset": "lastWeek" } } ] } } } ``` **Total CI/CD pipelines (click to expand)** Count the number of workflows/pipelines associated with a repository. ``` { "totalPipelines": { "title": "Total pipelines", "target": "githubWorkflow", "calculationSpec": { "calculationBy": "entities", "func": "count" } } } ``` ### Pull requests & code review[​](#pull-requests--code-review "Direct link to Pull requests & code review") **Open pull requests count (click to expand)** Count all open PRs for a service or repository. ``` { "openPullRequests": { "title": "Open pull requests", "target": "githubPullRequest", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "open" } ] } } } ``` **Merged PRs per week (click to expand)** Track the average number of PRs merged per week. ``` { "mergedPRsPerWeek": { "title": "Merged PRs per week", "target": "githubPullRequest", "calculationSpec": { "calculationBy": "entities", "func": "average", "averageOf": "week", "measureTimeBy": "mergedAt" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "merged" } ] } } } ``` **Average PR lead time (hours) (click to expand)** Calculate the average time from PR creation to merge. ``` { "averagePRLeadTime": { "title": "Average PR lead time (hours)", "target": "githubPullRequest", "calculationSpec": { "calculationBy": "property", "func": "average", "property": "leadTimeHours", "averageOf": "total", "measureTimeBy": "$createdAt" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "merged" } ] } } } ``` **Total lines changed (click to expand)** Sum the total lines added and deleted across all PRs. ``` { "totalLinesChanged": { "title": "Total lines changed", "target": "githubPullRequest", "calculationSpec": { "calculationBy": "property", "func": "sum", "property": "linesChanged" } } } ``` ### Issues & project management[​](#issues--project-management "Direct link to Issues & project management") **Open Jira issues count (click to expand)** Count open issues assigned to a service or team. ``` { "openJiraIssues": { "title": "Open Jira issues", "target": "jiraIssue", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "in", "value": ["To Do", "In Progress", "In Review"] } ] } } } ``` **Total story points (open work) (click to expand)** Sum story points for all open issues. ``` { "totalStoryPoints": { "title": "Total story points (open)", "target": "jiraIssue", "calculationSpec": { "calculationBy": "property", "func": "sum", "property": "storyPoints" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "!=", "value": "Done" } ] } } } ``` **High-priority bugs count (click to expand)** Count critical and high-priority bugs. ``` { "highPriorityBugs": { "title": "High priority bugs", "target": "jiraIssue", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "issueType", "operator": "=", "value": "Bug" }, { "property": "priority", "operator": "in", "value": ["Critical", "High"] }, { "property": "status", "operator": "!=", "value": "Done" } ] } } } ``` ### Security & vulnerabilities[​](#security--vulnerabilities "Direct link to Security & vulnerabilities") **Critical vulnerabilities count (click to expand)** Count unresolved critical and high severity vulnerabilities. ``` { "criticalVulnerabilities": { "title": "Critical vulnerabilities", "target": "vulnerability", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "severity", "operator": "in", "value": ["CRITICAL", "HIGH"] }, { "property": "status", "operator": "!=", "value": "RESOLVED" } ] } } } ``` **Total open vulnerabilities (click to expand)** Count all unresolved vulnerabilities regardless of severity. ``` { "openVulnerabilities": { "title": "Open vulnerabilities", "target": "vulnerability", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "!=", "value": "RESOLVED" } ] } } } ``` ### Incidents & alerts[​](#incidents--alerts "Direct link to Incidents & alerts") **Open incidents count (click to expand)** Count active incidents for a service. ``` { "openIncidents": { "title": "Open incidents", "target": "incident", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "in", "value": ["triggered", "acknowledged"] } ] } } } ``` **Mean time to resolve (MTTR) (click to expand)** Calculate the average time to resolve incidents. ``` { "mttr": { "title": "MTTR (hours)", "target": "incident", "calculationSpec": { "calculationBy": "property", "func": "average", "property": "timeToResolveHours", "averageOf": "month", "measureTimeBy": "resolvedAt" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "resolved" } ] } } } ``` **P1 incidents in the last month (click to expand)** Count high-severity incidents from the past month. ``` { "p1IncidentsLastMonth": { "title": "P1 incidents (last month)", "target": "incident", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "severity", "operator": "=", "value": "P1" }, { "property": "$createdAt", "operator": "between", "value": { "preset": "lastMonth" } } ] } } } ``` ### Infrastructure & resources[​](#infrastructure--resources "Direct link to Infrastructure & resources") **Running pods count (click to expand)** Count pods in running state for a workload. ``` { "runningPods": { "title": "Running pods", "target": "pod", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "Running" } ] } } } ``` **Average CPU usage (click to expand)** Calculate average CPU usage across related resources. ``` { "averageCpuUsage": { "title": "Average CPU usage (%)", "target": "pod", "calculationSpec": { "calculationBy": "property", "func": "average", "property": "cpuUsage", "averageOf": "total", "measureTimeBy": "$updatedAt" } } } ``` **Total memory allocated (click to expand)** Sum memory allocation across all pods. ``` { "totalMemoryAllocated": { "title": "Total memory allocated (MB)", "target": "pod", "calculationSpec": { "calculationBy": "property", "func": "sum", "property": "memoryAllocatedMB" } } } ``` ### SLOs & compliance[​](#slos--compliance "Direct link to SLOs & compliance") **Number of SLOs defined (click to expand)** Count how many SLOs are defined for a service. ``` { "numberOfSLOs": { "title": "Number of SLOs", "target": "serviceLevelObjective", "calculationSpec": { "calculationBy": "entities", "func": "count" } } } ``` **Failing SLOs count (click to expand)** Count SLOs that are currently not meeting their targets. ``` { "failingSLOs": { "title": "Failing SLOs", "target": "serviceLevelObjective", "calculationSpec": { "calculationBy": "entities", "func": "count" }, "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "Failed" } ] } } } ``` **Has PR template (boolean check) (click to expand)** Count PR templates to determine if one exists (use with calculation property to convert to boolean). ``` { "hasPRTemplate": { "title": "Has PR template", "target": "pullRequestTemplate", "calculationSpec": { "calculationBy": "entities", "func": "count" } } } ``` ### Costs & cloud resources[​](#costs--cloud-resources "Direct link to Costs & cloud resources") **Total monthly cost (click to expand)** Sum the cost of all related cloud resources. ``` { "totalMonthlyCost": { "title": "Total monthly cost ($)", "target": "cloudResource", "calculationSpec": { "calculationBy": "property", "func": "sum", "property": "monthlyCost" } } } ``` **Number of cloud resources (click to expand)** Count cloud resources associated with a service or environment. ``` { "cloudResourceCount": { "title": "Cloud resources", "target": "cloudResource", "calculationSpec": { "calculationBy": "entities", "func": "count" } } } ``` ## Limitations[​](#limitations "Direct link to Limitations") * The aggregation property value for all entities of a blueprint will be recalculated **every 15 minutes**. * The maximum number of entities based on the blueprint where the aggregation property is defined is **20,000**. * If the total number of connected target entities across all relations exceeds **2,000,000** after filtering, the aggregation property will not be calculated. * When calculating by property (`sum`, `average`, `min`, `max`, `median`), only **number** type properties are supported. --- # Array Array is a data type used to save lists of data. ## Common usage[​](#common-usage "Direct link to Common usage") Use array properties to store multiple values for an entity, such as: * Programming languages used in a repository. * Container image tags available for a service. * Monitoring tools configured for a service. * Links to runbooks and dashboards. Use [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md) when these values represent other entities in your catalog. ## Multi source ingestion[​](#multi-source-ingestion "Direct link to Multi source ingestion") By default, array properties use last-writer-wins behavior: each update replaces the full array. Enable **Multi source ingestion** to let multiple [connectors](/context-lake/ingestion/overview/.md) or API callers contribute to the same list without overwriting each other. This works for **string** and **number** item types only. In the [Data model](https://app.port.io/settings/data-model) or software catalog, turn on **Multi source ingestion** when creating the property. In the API, set `"union": true`. This setting cannot be changed after creation. ### Example[​](#example "Direct link to Example") The `service` blueprint has multi source ingestion enabled on its `vulnerabilities` property. For a `payments-api` entity: 1. **Monday** - the Snyk integration writes `{ "snyk": ["CVE-101", "CVE-102"] }`. 2. **Tuesday** - Dependabot writes `{ "dependabot": ["CVE-102", "CVE-203"] }` without removing Snyk's findings. 3. **Wednesday** - Alice edits the entity in the UI and adds `CVE-300`. Port saves her changes under `user:alice`. A **GET** returns the merged, deduplicated array: ``` { "vulnerabilities": ["CVE-101", "CVE-102", "CVE-203", "CVE-300"] } ``` Each writer only updates its own source key. `CVE-102` appears once even though both scanners reported it. ### Write and read values[​](#write-and-read-values "Direct link to Write and read values") **Write** (`POST`, `PATCH`, `PUT`) - send one source key per update: ``` { "properties": { "vulnerabilities": { "scanner-a": ["CVE-2024-1", "CVE-2024-2"] } } } ``` Use `null` instead of an array to clear a source's slice. Source keys must match `^[A-Za-z0-9._:@/-]{1,128}$`. **Read** (`GET`, search) - Port returns the merged, deduplicated array: ``` { "vulnerabilities": ["CVE-2024-1", "CVE-2024-2", "CVE-2024-3"] } ``` In the UI, you edit the assembled array directly. Port stores UI edits under `user:{userId}` automatically. Related property types [Calculation](/context-lake/data-model/setup-blueprint/properties/calculation-property/.md) and [aggregation](/context-lake/data-model/setup-blueprint/properties/aggregation-property/.md) properties read the assembled value. Multi source ingestion works the same way for [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md#multi-source-ingestion) and [owning teams](/context-lake/business-context/ownership/.md#multi-source-ingestion-for-owning-teams): each writer owns a source key, and reads return the deduplicated combination of all sources. ### Constraints[​](#constraints "Direct link to Constraints") * `items.type` must be `string` or `number`. * `minItems` and `maxItems` are not supported with `union`. * You cannot toggle `union` after creation. Create a new property and [migrate the data](/context-lake/ingestion/ingest-data-into-port/other/migrate-data/.md) instead. * Sending a plain array, multiple source keys, or an invalid source key returns `422`. ## API definition[​](#api-definition "Direct link to API definition") ``` { "myArrayProp": { "title": "My array", "icon": "My icon", "description": "My array property", "type": "array", "default": [1, 2, 3] } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myArrayProp" = { title = "My array" required = true string_items = {} } } } } ``` Array types in Terraform To set the type of an array property, you need to use the `_items` property type. For example, to set an array of strings, you need to use the `string_items` property type. ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myArrayProp" = { title = "My array" required = true string_items = {} # You can also set here default values } } } } ``` We currently support the following types of array items: `string_items`, `number_items`, `boolean_items`, `object_items`. ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesArrayPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( array_props={ "myArrayProp": BlueprintPropertiesArrayPropsArgs( title="My array", required=True, string_items={}, ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { arrayProps: { myArrayProp: { title: "My array", required: true, stringItems: {}, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { arrayProps: { myArrayProp: { title: "My array", required: true, stringItems: {}, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ ArrayProps: port.BlueprintPropertiesArrayPropsMap{ "myArrayProp": port.BlueprintPropertiesArrayPropsArgs{ Title: pulumi.String("My array"), Required: pulumi.Bool(true), StringItems: port.BlueprintPropertiesArrayPropsStringItemsArgs{}, }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## Validate array[​](#validate-array "Direct link to Validate array") Array validations support the following operators: * `minItems` * `maxItems` * `uniqueItems` `minItems` and `maxItems` are not supported on [array properties with multi source ingestion enabled](#multi-source-ingestion). JSON schema validations Array validations follow the JSON schema model, refer to the [JSON schema docs](https://json-schema.org/understanding-json-schema/reference/array.html) to learn about all of the available validations * Basic * Terraform ``` { "myArrayProp": { "title": "My array", "icon": "My icon", "description": "My array property", "type": "array", "minItems": 0, "maxItems": 5, "uniqueItems": false } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myArrayProp" = { title = "My array" required = true string_items = {} min_items = 0 max_items = 5 } } } } ``` --- # Boolean Boolean is a primitive data type that has one of two possible values - `true` and `false`. ## Common usage[​](#common-usage "Direct link to Common usage") Use boolean properties to record yes/no states, such as: * Whether an environment allows deployments. * Whether a service handles personally identifiable information (PII). * Whether a repository enables branch protection. * Whether a resource has public network access. ## API definition[​](#api-definition "Direct link to API definition") ``` { "myBooleanProp": { "title": "My boolean", "icon": "My icon", "description": "My boolean property", "type": "boolean", "default": true } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { boolean_props = { "myBooleanProp" = { title = "My boolean" required = true } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesBooleanPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( boolean_props={ "myBooleanProp": BlueprintPropertiesBooleanPropsArgs( title="My boolean", required=True, ) } ), relations={}, ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { booleanProps: { myBooleanProp: { title: "My boolean", required: true, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { booleanProps: { myBooleanProp: { title: "My boolean", required: true, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ BooleanProps: port.BlueprintPropertiesBooleanPropsMap{ "myBooleanProp": port.BlueprintPropertiesBooleanPropsArgs{ Title: pulumi.String("My boolean"), Required: pulumi.Bool(false), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` --- # Calculation property Calculation properties allow you to use existing properties defined on blueprints, either directly or by using relations and mirror properties, in order to create new properties by using the [`jq`](https://github.com/stedolan/jq) processor for `JSON`. Starting March 30, 2026, [**persistent** calculation properties](#persistent-calculation-properties) were introduced, which have various benefits compared to the legacy calculation properties. Port accounts created after March 30, 2026 have persistent calculation properties **by default**.
If your account was created before this date, you can [enable persistent calculation properties](#how-to-enable-the-feature) for your organization. ## Common usage[​](#common-usage "Direct link to Common usage") Use calculation properties to derive values from an entity's existing data, such as: * Build a dashboard URL from a service identifier. * Count items in a list of code owners. * Combine configuration objects from properties and mirrors. * Convert a numeric measurement into a display label. Use an [aggregation property](/context-lake/data-model/setup-blueprint/properties/aggregation-property/.md) when you need to summarize multiple related entities. ## Definition[​](#definition "Direct link to Definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * API * Terraform * Pulumi The `calculationProperties` key is a top-level key in a blueprint definition, alongside `identifier`, `title`, and `schema`. You can access properties as part of the calculation by using `.properties` ``` { "calculationProperties": { "myCalculationProp": { "title": "My calculation property", "type": "string", "calculation": ".properties.myStringProp + .properties.myStringProp" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { myStringProp = { title = "Source string" } } } calculation_properties = { myCalculationProp = { title = "My calculation property" type = "string" calculation = ".properties.myStringProp + .properties.myStringProp" } } } ``` * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintCalculationPropertiesArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={"myStringProp": {"title": "Source string"}} ), calculation_properties={ "myCalculation": BlueprintCalculationPropertiesArgs( title="My calculation property", calculation=".properties.myStringProp + .properties.myStringProp", type="string", ) }, ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myStringProp: { title: "Source string" } }, }, calculationProperties: { myCalculation: { title: "My calculation property", calculation: ".properties.myStringProp + .properties.myStringProp", type: "string", }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myStringProp: { title: "Source string" } }, }, calculationProperties: { myCalculation: { title: "My calculation property", calculation: ".properties.myStringProp + .properties.myStringProp", type: "string", }, }, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myStringProp": port.BlueprintPropertiesStringPropsArgs{Title: pulumi.String("Source string")}, }, }, CalculationProperties: port.BlueprintCalculationPropertiesMap{ "myCalculation": port.BlueprintCalculationPropertiesArgs{ Title: pulumi.String("My calculation property"), Calculation: pulumi.String(".properties.myStringProp + .properties.myStringProp"), Type: pulumi.String("string"), }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## Supported types[​](#supported-types "Direct link to Supported types") * Basic * Format * Spec Calculation properties support the following output types: `string`, `number`, `object`, `array`, and `boolean`. For example: ``` { "calculationProperties": { "myCalculationProp": { "title": "My calculation property", "type": "my_output_type", "calculation": ".properties.myStringProp + .properties.myStringProp" } } } ``` Calculation properties support the following output formats: `yaml`, `team`, `user`, `ipv6`, and `url`. For example: ``` { "calculationProperties": { "myCalculationProp": { "title": "My calculation property", "type": "string", "format": "user", "calculation": ".properties.user" } } } ``` Calculation properties support the following output specs: `markdown`, `open-api` and `async-api`. For example: ``` { "calculationProperties": { "myCalculationProp": { "title": "My calculation property", "type": "string", "format": "url", "spec": "embedded-url" "calculation": ".properties.text" } } } ``` ## Using `meta properties` in calculation properties[​](#using-meta-properties-in-calculation-properties "Direct link to using-meta-properties-in-calculation-properties") It is possible to use [meta properties](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md) as template values for calculation properties. For example, if you want to concatenate a template URL (for example `https://datadog.com`) with the `identifier` meta property: Given the following `notification-service` entity: ``` { "identifier": "notification-service", "title": "Notification Service", "properties": { ... }, } ``` And the following calculation property definition for the blueprint: ``` { "calculationProperties": { "monitorUrl": { "title": "Monitor url", "type": "string", "format": "url", "calculation": "'https://datadog.com/' + .identifier" } } } ``` The value of the property `monitorUrl` will be `https://datadog.com/notification-service` ## Using `mirror properties` in calculation properties[​](#using-mirror-properties-in-calculation-properties "Direct link to using-mirror-properties-in-calculation-properties") It is possible to use [mirror properties](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md) as template values for calculation properties. For example, if an entity has a mirror property called `owningSquad`: ``` "mirrorProperties": { "owningSquad": { "path": "microservice-to-squad.$title" } } ``` A calculation property that links to the slack channel of the squad can be: ``` "owning_squad_slack": { "title": "Owning Squad Channel", "calculation": "'https://slack.com/' + .properties.owningSquad", } ``` Calculation on calculation is not supported Calculation properties **cannot** be used as template values for other calculation properties. ## Colorized calculation properties[​](#colorized-calculation-properties "Direct link to Colorized calculation properties") You can colorize calculation properties according to their value, by adding a `colorized` key with the value `true` to the calculation property object. You can also add a `colors` key to specify the colors of the different values, otherwise, the colors will be chosen automatically for you. For example, if you want to colorize a calculation property called `status-calculation` with the values `OK`, `WARNING`, and `CRITICAL`: ``` "properties":{ "status":{ "type": "string" }, }, "calculationProperties": { "status-calculation": { "title": "Status", "type": "string", "calculation": ".properties.status", "colorized": true, "colors": { "OK": "green", "WARNING": "yellow", "CRITICAL": "red" } } } ``` *** Parameters with special characters Parameters that contain special characters (for example: `-`) or start with a digit (for example: `@/#/$/1/2/3`) should be surrounded with single quotes. ``` "properties":{ "prop-with-special-char":{ "type": "string" }, }, "calculationProperties": { "myCalculatedProp": { "title": "My Calculated Property", "type": "string", "calculation": ".properties.'prop-with-special-char'", } } ``` ## Examples[​](#examples "Direct link to Examples") Refer to the calculation property [examples](/context-lake/data-model/setup-blueprint/properties/calculation-property/examples/.md) page. ## Persistent calculation properties[​](#persistent-calculation-properties "Direct link to Persistent calculation properties") With persistent calculation properties, values are computed and stored in the background, as opposed to being computed on-request. **Benefits compared to legacy calculation properties:** These benefits require [persistent calculation properties](#how-to-enable-the-feature) to be enabled. * **Faster entities table**: Loading the entities table is much faster because calculations are not run on each request (for example, up to **10 times** faster for a blueprint with **100,000** entities and **3** calculation properties). * **Filter by calculation properties**: You can filter entities by calculation properties in search and the API—something that was not possible before. * **Richer visualizations**: You can create line charts on calculation properties and use drill-down pie charts based on calculation properties. * **Automation triggers and conditions**: Since calculation property values are stored like regular properties, any change to a calculation property counts as an entity update. This means [entity change triggers](/workflows/actions-and-automations/define-automations/setup-trigger/.md#available-triggers) will fire when a calculation property value changes, and you can reference calculation properties in trigger [conditions](/workflows/actions-and-automations/define-automations/setup-trigger/.md#conditions). Automation triggers and blueprint changes Automations are triggered only when an **entity's** calculation property value changes (for example, because the entity's underlying data changed). Editing the calculation expression on the **blueprint** itself like changing the JQ formula will **not** trigger automations for every entity of that blueprint, even though their values may be recalculated. ### How to enable the feature[​](#how-to-enable-the-feature "Direct link to How to enable the feature") For existing users only Port accounts created after **March 30, 2026** have persistent calculation properties enabled by default. The steps below apply only to existing users who have not yet enabled this feature. Before enabling, review the [breaking changes](#breaking-changes) below, or use the [helper script](#identify-calculation-properties-to-review) to scan your organization automatically. To obtain your bearer token 1. Open the [Credentials](https://app.port.io/organization/home?openModal=credentials) modal. 2. Click on the **Generate API token** button, and copy the generated token. ``` curl -L -X POST 'https://api.port.io/v1/blueprints/persistent-calculation-properties/register' \ -H 'Authorization: ' ``` ### Identify calculation properties to review[​](#identify-calculation-properties-to-review "Direct link to Identify calculation properties to review") Before you enable the feature, you can run a helper script that scans your Port organization for calculation properties that may need the updates described in [Breaking changes](#breaking-changes) below. The script is **read-only**: it only reads data from Port and does not modify blueprints, entities, or any other catalog data. The [persistent calculation properties script](https://github.com/port-labs/script-examples/tree/main/persistent-calculation-properties) in the `port-labs/script-examples` repository: * Connects to your Port organization. * Checks whether persistent calculation properties are already enabled. * Scans blueprint calculation properties for the same breaking patterns (relative `now` in JQ, and `.relations..title` or `.relations..identifier`). * Generates an HTML report under `output/` (for example `output/index.html`) with summary statistics and a table of properties that need review. See the repository README for prerequisites, `config.json`, and how to run `npm start`. ### Breaking changes[​](#breaking-changes "Direct link to Breaking changes") When moving to persistent calculation, two behaviors change and may require updates to your calculation properties. Updates may be required If you have calculation properties that use relative date (`now`) or relation titles (e.g. `.relations.relationName.title`), review the items below and update them before enabling the feature. #### Change 1: Relative date (`now` in JQ)[​](#change-1-relative-date-now-in-jq "Direct link to change-1-relative-date-now-in-jq") Calculations that use the `now` JQ function (relative to the current time) are no longer evaluated at request time. Their values are accurate to roughly an hour, not to the second or minute. **What you can do:** * If roughly hour-level accuracy is enough for your use case, no change is needed. * If you need second- or minute-level accuracy, do not compute the relative date inside the calculation property. Store only the **target date** in the property (e.g. from entity data), and compute the relative value (e.g. "minutes ago") where you use the property—for example in the UI or in a workflow. #### Change 2: Relation titles (`.relations..title`)[​](#change-2-relation-titles-relationsnametitle "Direct link to change-2-relation-titles-relationsnametitle") With persistent calculation, the evaluation context does not use the [Search entities](/api-reference/search-entities/) `attach_title_to_relation` behavior. So JQ that expects relation values to be objects with `.title` (e.g. `.relations.microservice.title`) will break, because in this context relations are **string or array of strings** (identifiers only). For the two response shapes, see the [attach\_title\_to\_relation parameter](/context-lake/consuming-the-lake/search-and-query/advanced/.md#attach_title_to_relation-example) in the advanced search docs. **What you can do:** * If you need the related entity's **title** inside the JQ, create a [mirror property](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md) for the title (e.g. `path: "relationName.$title"`) and reference that property in your calculation (e.g. `.properties.myRelationTitle`) instead of `.relations.relationName.title`. * Otherwise, write the JQ for identifier-only relations (string or array of strings). ### Limitations[​](#limitations "Direct link to Limitations") #### Calculations based on mirror properties[​](#calculations-based-on-mirror-properties "Direct link to Calculations based on mirror properties") [Mirror properties](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md) get their values from a related (parent) entity. When a parent entity's value changes, the calculation property in the child entity that depends on this mirror property is **not** updated in real time. It is only updated on the hourly sync process. This means: * If you update a parent entity and immediately read a child entity that has a calculation based on a mirror property from that parent, you may get stale data. * For API workflows that chain writes and reads, consider using [automation triggers](/workflows/actions-and-automations/define-automations/setup-trigger/.md) on entity updates rather than immediate follow-up API calls. No race condition for other calculations Calculation properties that do **not** use the `now` function or mirror properties are calculated before the entity is written to the database. For these calculations, when you receive a success response from a write operation, the calculation value is already accurate. ### Rollback the feature[​](#rollback-the-feature "Direct link to Rollback the feature") If you need to disable persistent calculation and return to on-request evaluation, send a single POST request to the unregister endpoint. It is a straightforward switch—nothing else in your catalog or blueprints changes beyond how calculation properties are evaluated. The request matches [How to enable the feature](#how-to-enable-the-feature), but use the `unregister` path instead. ``` curl -L -X POST 'https://api.port.io/v1/blueprints/persistent-calculation-properties/unregister' \ -H 'Authorization: ' ``` Use the same bearer token as in [How to enable the feature](#how-to-enable-the-feature). --- # Examples Here are a few examples of calculation properties use cases: ## Basic calculation property[​](#basic-calculation-property "Direct link to Basic calculation property") In the following example, we will create a calculation property called `MBMemory` of type `number`, and then transform it into a GB unit: ``` "properties":{ "MBMemory":{ "type": "number" }, }, "calculationProperties" : { "GBMemory": { "title": "GB Memory", "type": "number", "calculation": ".properties.MBMemory / 1024" } } ``` In this instance, if you have a `MBMemory` **string** property with the value: ``` { "MBMemory": 2048 } ``` Then the `GBMemory` calculation property value will be: ``` { "GBMemory": 2 } ``` This is an example of how calculation properties can be used to perform math equations over data ingested into Port. ## Concatenate strings[​](#concatenate-strings "Direct link to Concatenate strings") Assume you have two `string` properties: one is called `str1` with the value `hello`, the other is called `str2` with the value `world`. The following calculation will result in `hello world`: ``` { "title": "Concatenate strings example", "type": "string", "calculation": ".properties.str1 + .properties.str2" } ``` Use single quotes for templates If you want to provide your own string template to concatenate properties , wrap your template string with single quotes (`'`), such as `'https://' + .properties.str1'` ## Calculate array length[​](#calculate-array-length "Direct link to Calculate array length") Assume you have an `array` property called `myArr` with the value `["this", "is", "port"]`. The following calculation will result in `3`: ``` { "title": "Calculate array length", "type": "number", "calculation": ".properties.myArr | length" } ``` ## Slice array[​](#slice-array "Direct link to Slice array") Assume you have an `array` property called `array1` with the value `[1,2,3,4]`. You can use the following slicing calculation to get the result `[2,3,4]`: ``` { "title": "Slice array example", "type": "string", "calculation": ".properties.array1[1:4]" } ``` ## Merge objects[​](#merge-objects "Direct link to Merge objects") Assume you have two `object` properties: one called `deployed_config` with the value `{cpu: 200}`, the other called `service_config` with the value `{memory: 400}`. You can merge these two object properties and receive a unified config by using the following calculation: ``` "calculationProperties" : { "merge_config": { "title": "Merge config", "type": "object", "calculation": ".properties.deployed_config * .properties.service_config", } } ``` The result will be `{cpu: 200, memory: 400}`. Object merging * Object merging performs a deep merge, resulting in nested keys from the original objects appearing in the resulting merged object. * If the same `key` appears in one or more of the merged properties, the last property that appears will have its `keys` take precedence over the `keys` of properties that appeared earlier in the calculation. For example, Let's assume we have 2 properties with type `object`, and we want to perform a deep merge between them: ``` { "obj1": { "cpu": 200 }, "obj2": { "cpu": 400 } } ``` If the calculation is `".properties.obj1 * .properties.obj2"` , the result will be `{cpu: 400}`, If the calculation is `".properties.obj2 * .properties.obj1"` , the result will be `{cpu: 200}`, For merging YAML properties, the merging behavior will be the same, but if you specify `type: "string` and `format: "yaml"`, the result will be a YAML object. ## If-else conditions[​](#if-else-conditions "Direct link to If-else conditions") Assume that your services use multiple packages, some written in Python and some written in Node.js. By using an if-else JQ rule, you can specify a different URL for each package, based on its language: ``` "calculationProperties" : { "package_manager_url": { "title": "Package Link", "type": "string", "format": "url", "calculation": "if .properties.language == \"Python\" then \"https://pypi.org/project/\" + .identifier else \"https://www.npmjs.com/package/\" + .identifier end", } } ``` For the following entity: ``` { "identifier": "requests", "properties": { "language": "Python" } } ``` The result will be `package_manager_url: "https://pypi.org/project/requests"`. For the following entity: ``` { "identifier": "axios", "properties": { "language": "Nodejs" } } ``` The result will be `package_manager_url: "https://www.npmjs.com/package/axios"`. ## Calculate K8S labels[​](#calculate-k8s-labels "Direct link to Calculate K8S labels") You can create a calculation property inside your Blueprint to display a specific tag. Looking at the Node Blueprint, you can find the following property: ``` "properties": { ... "labels": { "type": "object", "title": "Labels", "description": "Labels of the Node" }, ``` And the labels object looks like: ``` { "kubernetes.io/metadata.name": "port-k8s-exporter", "name": "port-k8s-exporter" } ``` To display the value of `name`, create a new calculation property within the same Blueprint. then use the following JQ calculation: ``` .properties.labels."name" ``` The result will be a property that displays `port-k8s-exporter`. ## Calculate Cloud resource tags[​](#calculate-cloud-resource-tags "Direct link to Calculate Cloud resource tags") Assuming you have a property `tags` in your Blueprint, you can use JQ to display the value of a tag. In Port's AWS exporter, you can find the following array: ``` "tags": [ { "Value": "authentication-service", "Key": "server-application" }, { "Value": "1a23-4bc5d-67efg-89k10", "Key": "applyId" }, { "Value": "0.0.11", "Key": "server-version" } ] ``` To display a Value of `applyId`, create a new calculation property within the Blueprint of the entity, and use the following JQ calculation: ``` .properties.tags.[] | select(.Key=="applyId") | .Value ``` The result will be a property that will display `1a23-4bc5d-67efg-89k10`. --- # Datetime Datetime is a data type used to reference a date and time. ## Common usage[​](#common-usage "Direct link to Common usage") Use date & time properties to record timestamps from your tools, such as: * The latest successful deployment. * The start and resolution times of an incident. * A certificate expiration date. * The latest security scan. Port already tracks entity creation and update times in [meta-properties](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md). Use a [timer](/context-lake/data-model/setup-blueprint/properties/timer/.md) when a deadline should emit a timer-expired event. ## Display format[​](#display-format "Direct link to Display format") By default, Port displays datetime values relative to the current time (e.g., "3 months ago"). Use the optional `date_format` field to override this - for example, to show exact timestamps for audit logs, or to match your team's locale conventions. The following formats are supported: * `relative` - Relative to the current time, e.g., "3 months ago" (default). * `12-hour` - 12-hour clock, US convention, e.g., "Nov 26, 2025, 8:50 PM". * `24-hour` - 24-hour clock, international convention, e.g., "26 Nov 2025 20:50". * `YYYY-MM-DD HH:mm` - Fixed ISO-like format, e.g., "2025-11-26 20:50". ## API definition[​](#api-definition "Direct link to API definition") Use the `date_format` field to control how the value is displayed in the UI. See [display format](#display-format) for supported values. * Basic * Array ``` { "myDatetimeProp": { "title": "My datetime", "icon": "My icon", "description": "My datetime property", "type": "string", "format": "date-time", "date_format": "12-hour", "default": "2022-04-18T11:44:15.345Z" } } ``` ``` { "myDatetimeArray": { "title": "My datetime array", "icon": "My icon", "description": "My datetime array", "type": "array", "items": { "type": "string", "format": "date-time", "date_format": "12-hour" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. Use the `date_format` field to control how the value is displayed in the UI. See [display format](#display-format) for supported values. * Basic * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myDatetimeProp" = { title = "My datetime" icon = "My icon" description = "My datetime property" format = "date-time" date_format = "12-hour" default = "2022-04-18T11:44:15.345Z" } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myDatetimeArray" = { title = "My datetime array" icon = "My icon" required = false string_items = { format = "date-time" } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") Use `dateFormat` in TypeScript and JavaScript, `date_format` in Python, and `DateFormat` in Go. Version 2.29.0 exposes this setting for scalar string properties; array items support `format: "date-time"` without a display-format setting in these providers. Use the `date_format` field to control how the value is displayed in the UI. See [display format](#display-format) for supported values. * Basic * Enum - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myDatetimeProp": BlueprintPropertiesStringPropsArgs( title="My datetime", format="date-time", date_format="12-hour", required=True, ) } ), relations={}, ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myDatetimeProp: { title: "My datetime", format: "date-time", dateFormat: "12-hour", required: true, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myDatetimeProp: { title: "My datetime", format: "date-time", dateFormat: "12-hour", required: true, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myDatetimeProp": &port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My datetime"), Format: pulumi.String("date-time"), DateFormat: pulumi.String("12-hour"), Required: pulumi.Bool(false), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesStringPropsArgs,BlueprintPropertiesArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myDatetimeProp": BlueprintPropertiesStringPropsArgs( title="My datetime", required=True, format="date-time", date_format="12-hour", enums=["2022-04-18T11:44:15.345Z", "2022-05-18T11:44:15.345Z"], enum_colors={ "2022-04-18T11:44:15.345Z": "red", "2022-05-18T11:44:15.345Z": "green" }, ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myDatetimeProp: { title: "My datetime", format: "date-time", dateFormat: "12-hour", required: true, enums: ["2022-04-18T11:44:15.345Z", "2022-05-18T11:44:15.345Z"], enumColors: { "2022-04-18T11:44:15.345Z": "red", "2022-05-18T11:44:15.345Z": "green", }, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myDatetimeProp: { title: "My datetime", format: "date-time", dateFormat: "12-hour", required: true, enums: ["2022-04-18T11:44:15.345Z", "2022-05-18T11:44:15.345Z"], enumColors: { "2022-04-18T11:44:15.345Z": "red", "2022-05-18T11:44:15.345Z": "green", }, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myDatetimeProp": &port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My datetime"), Format: pulumi.String("date-time"), DateFormat: pulumi.String("12-hour"), Required: pulumi.Bool(false), Enums: pulumi.StringArray{ pulumi.String("2022-04-18T11:44:15.345Z"), pulumi.String("2022-05-18T11:44:15.345Z"), }, EnumColors: pulumi.StringMap{ "2022-04-18T11:44:15.345Z": pulumi.String("red"), "2022-05-18T11:44:15.345Z": pulumi.String("green"), }, }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` --- # Email Email is a data type used to save Email addresses. ## Common usage[​](#common-usage "Direct link to Common usage") Use email properties to store contact addresses, such as: * Team support mailboxes. * Security contact addresses. * External vendor contacts. Use the [Port User property](/context-lake/data-model/setup-blueprint/properties/user/.md) when you want to display a person's name and avatar. You can store internationalized email addresses with `"format": "idn-email"`. ## API definition[​](#api-definition "Direct link to API definition") * Basic * Enum * Array * Basic (international address) ``` { "myEmailProp": { "title": "My email", "icon": "My icon", "description": "My email property", "type": "string", "format": "email", "default": "me@example.com" } } ``` ``` { "myEmailEnum": { "title": "My email enum", "icon": "My icon", "description": "My email enum", "type": "string", "format": "email", "enum": ["me@example.com", "example@example.com"], "enumColors": { "me@example.com": "red", "example@example.com": "green" } } } ``` ``` { "myEmailArray": { "title": "My email array", "icon": "My icon", "description": "My email array", "type": "array", "items": { "type": "string", "format": "email" } } } ``` ``` { "myInternationalEmailProp": { "title": "My international email", "icon": "My icon", "description": "My international email property", "type": "string", "format": "idn-email", "default": "a-mí@उदाहरण.com" } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Enum * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myEmailProp" = { title = "My email" icon = "My icon" description = "My email property" format = "email" } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { myEmailProp = { title = "My email" required = false format = "email" enum = ["me@example.com", "example@example.com"] enum_colors = { "me@example.com" = "red", "example@example.com" = "green" } } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { myEmailArray = { title = "My email array" string_items = { format = "email" } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic * Enum - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myEmailProp": BlueprintPropertiesStringPropsArgs( title="My email", format="email", ) } ), relations={}, ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myEmailProp: { title: "My email", format: "email", required: true, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myEmailProp: { title: "My email", format: "email", required: true, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myEmailProp": &port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My email"), Format: pulumi.String("email"), Required: pulumi.Bool(true), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myEmailProp": BlueprintPropertiesStringPropsArgs( title="My email", required=True, format="email", enums=["me@example.com", "other@example.com"], enum_colors={ "me@example.com": "red", "other@example.com": "green", }, ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myEmailProp: { title: "My email", required: true, format: "email", enums: ["me@example.com", "other@example.com"], enumColors: { "me@example.com": "red", "other@example.com": "green", }, } }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myEmailProp: { title: "My email", required: true, format: "email", enums: ["me@example.com", "other@example.com"], enumColors: { "me@example.com": "red", "other@example.com": "green", }, } }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myEmailProp": &port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My email"), Required: pulumi.Bool(false), Format: pulumi.String("email"), Enums: pulumi.StringArray{ pulumi.String("me@example.com"), pulumi.String("other@example.com"), }, EnumColors: pulumi.StringMap{ "me@example.com": pulumi.String("red"), "other@example.com": pulumi.String("green"), }, }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` --- # Embedded URL The `embedded URL` property is used to embed and display a webpage within an [entity]() in Port.
Using this property will automatically create an additional tab in each [entity page](/interface-builder/port-interface/page/entity-page/.md), displaying the embedded content. In the following example, we see the `Shipping` entity page, which is an instance on the `Domain` blueprint.
The blueprint has an `embedded URL` property named `Architecture`, which is automatically displayed in a dedicated tab: ![Entity page embedded URL Architecture tab](/img/software-catalog/blueprint/embeddedUrlExample.png) ## URL type[​](#url-type "Direct link to URL type") Port supports the following URL types: * **Public link** - A link to a public webpage, which does not require authentication. * **Private link** - A link to a webpage that is protected by SSO authentication. To use this type, you'll need to provide the required parameters, see the [authentication](/context-lake/data-model/setup-blueprint/properties/embedded-url/authentication/.md) section for more information and examples. ## Common usage[​](#common-usage "Direct link to Common usage") Use embedded URL properties to display external content on entity pages, such as: * Monitoring dashboards for a service. * Architecture diagrams. * Charts from observability tools. The source website must allow embedding. Configure [authentication](/context-lake/data-model/setup-blueprint/properties/embedded-url/authentication/.md) for supported pages that require sign-in. ## Schema definition[​](#schema-definition "Direct link to Schema definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * API * Terraform ``` { "myEmbeddedUrl": { "title": "My Embedded URL", "type": "string", "format": "url", "spec": "embedded-url", "description": "embedded-url Prop", // specAuthentication is needed only when using a protected/private URL "specAuthentication": { "authorizationUrl": "https://app.com", "tokenUrl": "https://app.com", "clientId": "1234", "authorizationScope": [ "api://xxxx-xxxx-xxxx-xxxx-xxxx/user.read" ] } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { myEmbeddedUrl = { title = "My Embedded URL" required = false format = "url" spec = "embedded-url" } } } } ``` ## Examples[​](#examples "Direct link to Examples") ### Datadog dashboard[​](#datadog-dashboard "Direct link to Datadog dashboard") In this example we are embedding a [Datadog](https://docs.datadoghq.com/dashboards/sharing/) dashboard in order to get application metrics directly inside Port. Add the `embedded-URL` property to a Blueprint: **Blueprint property definition (click to expand)** ``` { "datadog": { "title": "Datadog", "type": "string", "format": "url", "spec": "embedded-url" } } ``` Create or edit an Entity of the Blueprint you added the `Datadog` property to, and specify the URL to the Datadog dashboard: ![Entity edit form with a shared Datadog dashboard URL in the Datadog property](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAl4AAAA5CAYAAADnTyVNAAAAAXNSR0IArs4c6QAAAGJlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAABJKGAAcAAAASAAAAUKABAAMAAAABAAEAAKACAAQAAAABAAACXqADAAQAAAABAAAAOQAAAABBU0NJSQAAAFNjcmVlbnNob3TRi2WEAAAB1WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj41NzwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj42MDY8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpVc2VyQ29tbWVudD5TY3JlZW5zaG90PC9leGlmOlVzZXJDb21tZW50PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KBjNyRwAAGhNJREFUeAHtnQWYLMXVhgsLcnG34O5uCe7uTnD34AQL7q4J7u4Owd1JcL1IcCeQBJ2c9/z3zF/b9NjunZ1d7neeZ6a7y+ur6qqvTp3uHqpikiRCQAgIASEgBISAEBACbUdg6LbnoAyEgBAQAkJACAgBISAEHAERL3UEISAEhIAQEAJCQAj0EgIiXr0EtLIRAkJACAgBISAEhICIl/qAEBACQkAICAEhIAR6CQERr14CWtkIASEgBISAEBACQkDES31ACAgBISAEhIAQEAK9hICIVy8BrWyEgBAQAkJACAgBISDipT4gBISAEBACQkAICIFeQkDEq5eAVjZCQAgIASEgBISAEBDxUh8QAkJACAgBISAEhEAvITBsL+WjbISAEGgDAv/+97/Tl19+mb799tv03XfftSEHJSkEhIAQ6L8IDD/88GnAgAFp9NFHTyONNFKfqMhQ+lZjn2gHFUIItIzA+++/n/71r3+lscYaK40yyijpN7/5TRpqqKFaTkcRhIAQEAK/RgT4FPX333/v4+Rnn33m4+SEE07Y8aqKeHW8CVQAIdA6Am+99ZYTrQkmmEBkq3X4FEMICIEhDAFI2AcffOBEbLLJJuto7UW8Ogq/MhcCrSOApgvpCyu31kuvGEJACAiBziHQF8ZPGdd3rv2VsxBoGQFsutheRNMlEQJCQAgIgdYQYOxkDGUs7ZSIeHUKeeUrBLqBAIb02HTJlqsb4CmKEBACQzwCjJ2MoYylnRIRr04hr3yFQDcQ4OlFDOklQkAICAEh0D0EGEMZSzslIl6dQl75CoFuIMArI3h6USIEhIAQEALdQ4AxtJOv3xHx6l67KZYQ6BgC2mbsGPTKWAgIgV8BAp0eQ0W8fgWdSFUQAkJACAgBISAE+gcCenN9/2gnlVII9BiBhx9+2NN45JFH0q677trj9JSAEBACQkAItI6AiFfrmCmGEOhXCBx77LHpuOOO61LmuN5ll11EwrogowshIASEQHsRGObPJu3MgrfFHnDAAemee+7x3wsvvOCPcY477riJbyg1El73/+677/p3lhqFLfp//PHHPuEsvPDCRS9dC4F+iQB9mnunWVljjTXSlVdeWQ0O0Zp//vkTWi8kjgsssEA1THdO7rzzzjTyyCP7rzvx2x0Hbd8ee+yRwCOkzC38unP85JNP0gMPPJAmn3zyNPTQvWfFQRv+5z//8Ufku1PuYpyvvvoq3XvvvWnSSSdNwwwzTNG749dbb721l2vqqaeuWxbal/ljzDHHrBsOz1pp/vOf/0xnn3124k3nzT5N/I9//CM988wz6fXXX//Fb4oppmjqVTC85PPuu+9Ozz//vH9fkO8M1pNm+t4rr7zi5Zl44onrJdW038CBAxP3/dtvv53GGGOMut9BPO+88xJlnHLKKeumX9aXX3rppfS3v/0t0RaN8qmbeMGz1bG0EL1nl3yrsZ3y008/VbbccsvK008/XbEGqjz66KOVE088sXLIIYdU7AVmDbM20lXZcccdG4YrC2Cd1/Mu85ObEOiPCDz33HNNF/uYY46p2NvtKw899JAfuQ5ZffXVK/wiDOfdlZ9//tnTt0G4ZhLc+3vuuWfFXlxYDVPmVvUczCe2vqxstdVWXVItc+sSoMWL+++/33H473//21RMIzcV0zw2FbZeoBVWWKFiWs16QVrye+KJJ7we9p6jluL1RmD73p6X7eWXX26Y3SKLLFI544wzGoarleYPP/xQWW655Ty/m266qWE6EeDAAw+szDLLLF1+3If8jAhGsJrH2267zcPOOeecld///vd+fsEFF9QMj0czfe+oo46qrLzyynXTadaT8lAfyshxmmmm8Tm+LL6RJg9DuEZS7MuHHXaYx6UtwZR8nnrqqUbJNOXfyljaVIItBOq1Zdn444+fJplkkjTvvPOmbbfdNo0wwgjp9ttvr7JGAyGdddZZ6eSTT0533XVXsjqkL774Ip1yyinJBjL34/t0SFlY9xjkd+655/oqJT4NEH6sCu+444502mmnJbuRPP3ws8nDV3n4XX755envf/+7s/nw11EI9CcEYnsRDVeZ4M7qEu0XGjHOidMu4QO1F154YZdHuMvc2pU/K+ai5rvMrV35l6XLSv6GG24o85JbDQRs4Z7GHnvsZBNwjRCtO9dK86KLLnINUasp7r///j5/MIfEb7HFFku/+93v0nDDDdcwuX322Sf94Q9/SEaAXYNqC5a01157uVazYeReCICmi/KcdNJJ6cknn0xo0maccca07777/iL3b775JlF+hHZrRdjpYv4/9dRTfbfMCFeaZ5550hFHHNFKMn0ybK8Rr7z2ww47bDImn9544w13ttWLA4w6F2BRXzIRjDjiiNXBkq0Q3jZbKywJoeKFvKHq58a89NJLq9lCrP7yl794J5l77rkTg/7xxx9f/WzANddc48TLGLyrTCFgUb5qIjoRAv0EgbDh4rjmmmt6qTmfaKKJ/BduHOM84nS3ip9++qnbiy266KJp880393uNtDAzsJWsJ7v00ksn03aXuhHg6quv9kGd44orrphWWWUVXyj9+OOPHp8/tgh22223RD6bbrppF/LCGHDFFVdUw3LCh3HffPPNlG+nFt1MS5HOP//8ZFoBT3fjjTf2xVmXhAoXbANRDtI1bZrnkwfhPUGnn356Wn755T3M9ttvn9555x0PwgLz4IMPTq+++qrX8/rrr3f3enEIYDsIPo6tttpqaYkllvBFokfM/pjsGNvAb6211kpnnnlmyvEj6K233urYUXbag7GSyTSXZ5991tsRnJk8+cxKSFk5wPDiiy+OIL84Pvjgg2mnnXZKCy64oOMF8QyhvXfeeee49CMLASb3XEyzkyAx8ToA4q277rqOBfX46KOP8uA+vmNNQx1oI/piUYpp4s+iHQJEG5UJ5CPSBWPTUpUFczfICduG9JWQSy65JG244YZebggL27sIygH6pml4qnWk7MiHH37oR/4a9T3CUCbuD9r40EMP9W1X3ENQYIAJ7UEfZWHUjNBXWLCZltyDY2Jgu1hpm222+UU/ow15Zxb9Z9RRR+2SfFkfygPEvQJXQOANLJ4gev1dOkK8AG2cccapDkLTTjuts1gGEkjRDDPM4AMlWrGZZ57ZtWO4scdeKyxpcgOtuuqq3mnpTJyHsD/MzWLbnp4HnR5ih80ZpAyyx2SBRo4Ba7755ouoOgqBfokAg2PYdFEBzvMf/rk75/Hko3u0+MeTkuONN55PKAyOTEhM+FNNNVXaYostPDXIh23flLoRgEmHCeDwww9Pti3iEyaD+kEHHVQtzXrrrecTzw477JDmmmsuH/DRWiCQxz/+8Y++sIoI1Invs2GzFFJ0Y0H3pz/9KZEOZcUOhknplltuiShdjkwKTDzvvfdeohyMTeSbC5MdZACiQBjyYCJEGJ9Ca8OENeuss7p7vTgEOPLIIx0L4q+//vo+4ZkZh8flD7xJDwLEOMaka9vJThIiEFp/xjpsBSkXZBVSQPlyoT2Z9CDN1113Xdp7772r3mXlgEwUiU9EeOyxx9Laa6/tYzl58p08xnvaG+EImcjltddeq84R4Z5rKRmzzQzF+wD9GoJD2uyWhFB3FtCM+4z3G2ywwS/6eJ5mxIMMoaGKRUm4c4T4404fp48wJ2222WY1d0ggHwsttJArFYiPBmf33XdPtnXmbXjfffd5X6LclBHSdc4557hN1Ndff+1Em76LQgFppu9RJ8oUbYzNGcqEEOyylllmGb9PtttuO+8nEG/6RiOBkIPNX//6V7eZ5J6GRNNPIEchaPsIQ/2pV1HK+lDel+eYYw6PQhoQUspMv1522WWLSfW/6xa2JbsVNGy8sLfK5fHHH68Y8O5kr+6vXHbZZZX99tvPbbKw6bKO535FG696YW2gqNhNVs3GtiarNl7YuWBbloutjCu2YqrYYOHhsFUJsY5bsdVqXOooBPoEAs3YJYTdVhQY+60yG67cPezAOLYq3DfYb2CPERI2J6bRcSdsPAljWrEI4jYhRTfTBHk4IwPVcDfeeKO7YYvD/U8cMweo+mPzYWYJfm2TUgXbqVwYT0x7kTu53Wjuts4667gNiU101XBGNCo2CVav8xMjhm5/YxNC1dkmYS9b2HgZkelij4INHGWPsjK+FNOvF4eyEd+2hqt5Eh43m9zczYz7/Rr3kMgXbBDTOlYYK0OwtTUSXrX/CRsv09JEkAr2QdjXIM2Uoxpx0Am2RUZmqs7YT2G/RF9FaPfFF1+86s8JeOblNK2l1800Px6OuNj9MMcg9C0jGH7OH9jyMy1i1c3ISMWIe/W6mCYeN998s+eDHwK+uY0XWFPWPF3T6FTx80iD/pjniM8RAWuujUAMClGpGIGrGHH0eQhH5krqRbj45e3ZTN/DNo00Q7hvsMcKGy/KYYuKSt5/qRP2aQjtg41f/sMNoa/QFzgyp0a/Zw4PISzpRfsZefL2Dv9m+1CMI4EDeRrxjWQqpkGtYBfGr1VpZixtNc1mw/8/Pe1FzmiF8/3rYPCovWGzPFmCLRi2X7laNS9avbBsRYbKljjWuNWoPBXy+eefV685ISyr8XhihI9m8tQEwlakRAj0RwRscPJio9VB49GMRDi2+OO8mXh5mNlmm616Gec8ydTo6bNqpEEn2IKg6Q6JrZYXX3zRNTBowtAusXXF1gPbJGjHkd/+9rf+i7hos9lyQWsWUuaGn03SXZ5cY1uU7UdW28UVO6t5NDaRL/GXXHLJZCSRUxfGFjQ9bJehbeccYdyJMccdsr96cdC0IDahVWOgbUGbF4L2Ck0a7iHUC0GjhAaELaZ824u6oUErbuGgkQlBs3jCCSckm1CrmrF65Yh4HNHCYa9E+XONCFtNbLU2K/Rn6oZWFaF90HDSB9A+ch39LtKkjPknttCWoNlkDmK7spgmcwaaPbbGYn6KtOJIXdDwoJUNoR751mm4H3300a7tYicHiTBLLbVUBPE6oRVC6GvYd7Etx1YnZeepSrTHbFcyxzXqe7QRWim2dUNGGmkk7988aYnQ5pjVoMmk3dEKUra479iSZbs9F2xBGRuMxHm/576K7UPmbVt4uVaTOJQZG62rrroqT6J6bkTSz+v1IcpkCyLv72hLmb/RYKLd5L7kyWHGhmKbVzPpwye9RrxQLWMjwN45gzsqelTlCB2FDkXj0fHxj07PKycwrsd+hMeC64WdaaaZfGuAR1Z5DDrfJsB+DGN9W9H7NiI3CjcQ2x507tlnn90bE3UppAsDf9wkQqC/IsB9FD/IWAzuUR/8wp1zxFao4d3yMTcc7snrFPJ0KERcMykhGNyutNJK6dprr/VJlG1IW22n6aef3v3zPyYVbJ5y04EyN+JEPhE/JmzsrorEizSLr8PJSRhpMPFRRsaU6aabzsc0tlfqSb04jKFIlCvSyctGubC5yQWygxsTJqQTKb4moqy9cpKU+zdTjjx/xu/IM8+XsZcHrmpJ0S4NUxLIbgjjvWmSkj1h5+QakrPJJpv49m6EyeuAG20GRpA+/IppYhvHXMN8kdttQVAguGx/MY8geV1w50e64c62NfNN2O8RJ+zkin0FP4TyQIDYNg1CDaGEkPMgBvVr1Pd4hQZSr39Cvtlq5PUWkBe2w3NlA+OCaY49nfjDPhRhIcUcG6QLN8qILSFbzfQx7klMfcIEAFs87CzBlLSb6UM83ED5eFguMOUeZ+HFIoz257w/Sq8RL4xWEToTgxCsdbTRRnM3Gp4Oj20CHZJOH8aT2IJhcA/7xy6hXlhWM2jK2D9HuElpbATGjz0ATJkbFZsM0jMVpvvTobm5aGxWhZGWe+pPCPQjBEJjlRvLBwErVqOWezHc4LpmYipK0Q17Hwbw0GywwkcYNwjLqh2NARMHkxD3Kga/uR1Y5MHEx4o41zCVuRGep6byiTMmvzxupItNFpNKaE5wx44mhHJButB2oY1BeMdXUXJy0ShOEEvyCYNjtAAxxpE2Wiq0e2jVYnwNoslEBUkjHeyjGEsRiBHaQxa/zUgz5cjTgfQxgaK9wJ4ohF2OmMyZxCEcTMiM1bErghYRASeIAPY+IexQME+goeKHjRljP+M8YzhCG+bCNQQJ0lWWJooBNK5m9pJH80U8k7+9nsLnI7DKn+JDm0e5gyAQGe0M2ka0hSHYKiO0IVpGBDJGe6D9CWKW9znajDJRX6RR3xswYIDjTV3RBiKQIa6D8Nl2qts8otkKcgo5CqHNammq0TpyT+Z9H3s8BFwgdZSX/h59HjKLoElkDm6mD6GEYf7PMeUayXez3KG//Rl4fUKsY/h+st0MpeVhP90GRfdrFBYbi3rvS2G/uyjsq+fu9sK3it3IxWC6FgIdRaBZuwTsX7CLMJJRfY8X5yG5f5xz7I5wP5IXtkQhRiKq+eNmD7f4NXZNYTtT5hY2Xma8XKGuRm7cNgTbFPLBbpO8bKFWMa15BTskrk0L5lljG2oG1G6jggPnxXqVuWHjRTp/tvd9GbGrhF1ZxLUtU7ePwS4GoVyEx06V8Lal4te4Mf4wVmEHg32ZEQy3Z7OVvoehDogRM7+mDqTfTBzKSTq0JfhQF/IMGy9scrDl2WijjSo2OVZse9NturCBsd0Cz9cWmB4HuyTexxR1B2MkbLxIK8RIj8eJcbVROcCHPMEGwcaHcmJTy1hrmg+/ti0p98dOD3/wAg8zkvfrsBEKG0H6VQg2eGBBPelTxCWNsFsy0uPXtgBxfyPnfm0TvydRlmaknR9JM7fxirJi9zZw4EC3K8NezRbv1Wi0D/HAsiiEo42M7HrfMDLqfYV+Y4sOjxfvvqSNsUUkLeqJNOp7hDHC73HoY7SBPbTh19HGpmDwa8qAHXXcd/ZkKdHrCnWmPPQf29Ks9n0zzq8Zr2jjRcBGfcjMijwf7nUj5d6XTYvmWOX2dTUzbeDR7FjaIJluecNaJYYAHQPjfgZWSBcd31aKwkYI9CkEWhksMJ5ngIQ85Ib0VAg3/GKCCILRnco2Q7xI1x7N9zxj8C9zCyNr01x4WMrIAI3hdAjG5UFi8GfCjQmZyQDCYxofn4DxN61eRC11w5M8SAejZOLwYyKJhWBMNvkDOhj4hxE0ecbkFZMCRBT3SA+iwHkQL1u1u6E3boF/ozgY5mMcHmliXEzbQi5CmGjBOMJAznL8CAeRgABQX4y17enLquF1M8SrrBykFQQwyB0kIYQyBl6UDZIbRI4w0SfxAyvbkagaZ0MkqGcu4BfEkzikDUEMgXhhLA4pwp+2oK4hZWmGX34kbk688IMURLr4U44w+sefsuZG/LiF0FfDIJ249GVIYAjnpv2qth/5QEZzadT3IPHMZ9H/bCvQCRzEBTHNmrc/+fPDH79miBfxKQ/kkbjkwX0XxB7/opQRr7I+VOzLkOXIh7woI/fi4JBWxtLBkV+ehojXIDToqKxkGDAY/FhNS4RAX0Og1cEin8wYuLguuhUntHbWGW2EbSd1ySJ3C+JFAAZmJtdawtPI+cQd4WICCFIZRAj/MjfcIV4QQ4QJtF66HmjQH6STp6wYP8oE4oZGIbQwZWGYBKPM+DcTh8m7HjakQ7pFrHFHs4K2MRf6AESnVaEMUQ40XEG8SCevU6QLXuBb5hdxyrBnwg2tZqQVR7BFY1hLyBMtWzHPemnWSqvoHk/aFt2buaZf2lZxzaC0HW1YSxr1PeJRZ8pYS9Bqch91V8C9iGuraTXTlylnfh+3mkdZ+FbH0rI0uuvWazZefX0LFuPRMJDs62VV+YRAswiEsXzYe8Ux4mNrGWHCrZ3HsDHJ8yhzwz+3c8nDx3nY8cR1HMNmhXSNZHYxRi9zi3hxDNuyuI5jpBvXHLExCruT3D3OsU9p9G28ojF8M3Gw42kkxXQjPC8F5enLA+wbupQNOxxs/YoPX0T4esd63y+shVctfMmnLA7upr3yp1c5LwptWqsPEZY2CkP1PG69NPNw9c6b+Q5krfg8JFF8UCIPmz80kbvHeaO+RzjwrFfGsAOMNFs9YsvVU2mmL/e0nD0t4+COL+I1uBFVekKgjyEAsQpyxeRqWxtewjDC70vFZYCt96RbK2UtW0iVuZEmT1TXm6Baybevh7VtIZ+QedM8Rs+2VeRvaOeR/Z7IpPaSz3ZhaNvAPSlaadx2pFmakRyFQAGBoVCVFdx0KQSEQB9FgHcx8XSaRAgMDgQY/tGcSITAkIZAJ8fSoYc0sFVfISAEhIAQ+D8ERLrUE4RA7yMg4tX7mCtHIdAjBKSk7hF8iiwEhMAQjkCnx1ARryG8A6r6/QsB3kZtT331r0KrtEJACAiBPoQAY2jxzf69WTwRr95EW3kJgR4iwBNA9oh5D1NRdCEgBITAkIsAY2gzT1O2CyERr3Yhq3SFQBsQ4BULfFOt06ryNlRNSQoBISAE2o4AYydjaKPX1bSzICJe7URXaQuBwYwA34Pj3Ul8z1AiBISAEBACrSHA2MkYyljaKRHx6hTyylcIdBMBewO923nxQV9pvroJoqIJASEwRCHAWMmYiX0XY2gnRe/x6iT6ylsI9AABBhFsFcYaayxfwfEWbL0eoAeAKqoQEAK/KgQgWxAtxkm2F9F0dZp0AbCI16+qm6kyQxoC9j23ZN8xS99++22yb5kNadVXfYWAEBACdRHg6UUM6bHp6uT2Yl5IEa8cDZ0LASEgBISAEBACQqCNCMjGq43gKmkhIASEgBAQAkJACOQIiHjlaOhcCAgBISAEhIAQEAJtREDEq43gKmkhIASEgBAQAkJACOQIiHjlaOhcCAgBISAEhIAQEAJtREDEq43gKmkhIASEgBAQAkJACOQIiHjlaOhcCAgBISAEhIAQEAJtREDEq43gKmkhIASEgBAQAkJACOQIiHjlaOhcCAgBISAEhIAQEAJtREDEq43gKmkhIASEgBAQAkJACOQI/A+MJuEbC1PQOgAAAABJRU5ErkJggg==) Now go to the specific entity page of your Entity and the Datadog dashboard will be visible in a dedicated tab: ![Datadog application metrics dashboard embedded in a dedicated Port entity tab](/assets/images/datadog-c2f969d502d2aa1e80668f1584755b1d.png) ### New Relic chart[​](#new-relic-chart "Direct link to New Relic chart") In this example we are embedding a CPU usage [New Relic Chart](https://one.eu.newrelic.com/) to get infrastructure metrics directly inside Port. Add the `embedded-URL` property to a Blueprint: **Blueprint property definition (click to expand)** ``` { "cpuUsage": { "type": "string", "title": "CPU usage", "spec": "embedded-url", "format": "url" } } ``` Go to new relic and extract the chart URL of a specific chart ![New Relic CPU usage chart menu with the Get chart link option selected](/assets/images/GetEmbedUrlNewRelic-e5eb1f6949302d2a9cb5d3cbb2351092.png) Create or edit an Entity of the Blueprint you added the `cpuUsage` property to, and specify the URL to the CPU Usage chart: ![Entity edit form with a New Relic chart embed URL in the CPU usage property](/assets/images/editEntityNewRelic-e9eee5a55f5ca8517248ceb75c8e100f.png) Now go to the specific entity page of your Entity and the CPU Usage chart will be visible in a dedicated tab: ![New Relic CPU usage chart embedded in a dedicated Port entity tab](/assets/images/new-relic-84de49a9e2ea2ff834011c6b87f28cef.png) --- # Authentication With this feature, you can embed another website that's protected by SSO authentication. To do this, you'll need the required parameters to create a JWT token. ## Authentication code flow + PKCE[​](#authentication-code-flow--pkce "Direct link to Authentication code flow + PKCE") The following diagram outlines the login scheme used to authenticate with your SSO and gain access to the private resource: ![PKCE authorization code flow between the user, Port widget client, authorization server, and embedded application](/assets/images/AuthorizationCodeFlow-dfc247f27206d592e984bac1c3e13d72.png) Here is an explanation of the login flow: 1. The widget will generate a PKCE `code_verifier` & `code_challenge`. 2. The widget URL is set to the `authorizationUrl` along with the `clientId` and the generated `code_challenge`. 3. The widget will then be redirected to the SSO sign-in page. 4. The user will sign in using the SSO (If the user is already signed in to the SSO, this step will happen automatically). 5. The SSO sign-in page will redirect the widget back to with the authorization `code` as a URL hash parameter. 6. The widget will send the `code`, `clientId` and the `code_verifier` to the `tokenURL`. 7. The SSO will validate the PKCE code. 8. A response will come back with an access token. 9. The widget will pass the access token as a query parameter `auth_token={accessToken}` to the URL specified in the property value. 10. Your page should be displayed now! ## Required parameters[​](#required-parameters "Direct link to Required parameters") To set up the authentication, you'll need the following parameters: * `clientId` * `authorizationUrl` * `tokenUrl` Here's an example of how to apply these parameters in your Blueprint: ``` { "title": "Grafana", "type": "string", "format": "url", "spec": "embedded-url", "specAuthentication": { "clientId": "your-client-id", "authorizationUrl": "your-authorization-url", "tokenUrl": "your-token-url" } } ``` ## Examples[​](#examples "Direct link to Examples") ### Okta[​](#okta "Direct link to Okta") **Setup (click to expand)** **Steps:** 1. Follow the steps in [Okta's documentation](https://developer.okta.com/docs/guides/implement-grant-type/authcodepkce/main/) to create an Application in your Okta Organization. 2. Make sure the Port host is in the `Redirect Uris`: 1. Go to Applications -> The application you just created -> Login. 2. Add `https://app.port.io` as a Sign-in redirect URI. 3. Enable IFrame for Sign-In Page: 1. Go to Customizations -> Other. 2. Scroll to "IFrame Embedding" and enable it.
**Configure Grafana with OAuth & Port embedding** Example values are for illustration only The following example is just for illustration purposes and may not reflect the actual URLs and client IDs used in your Okta setup. Based on Grafana docs for [JWT Configuration](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/jwt/) & [OAuth Configuration](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/generic-oauth/) ``` [security] ;Required for the embedding allow_embedding = true [auth.jwt] ;Required for the embedding ... jwk_set_url = https://{your-okta-org}.okta.com/oauth2/default/v1/keys expect_claims = {"iss": "https://{your-okta-org}.okta.com", "aud": "https://{your-okta-org}.okta.com"} url_login = true ... [auth.generic_oauth] ;Regular OAuth authentication ... client_id = {CLIENT_ID} client_secret = {CLIENT_SECRET} auth_url = https://{your-okta-org}.okta.com/oauth2/v1/authorize token_url = https://{your-okta-org}.okta.com/oauth2/v1/token api_url = https://{your-okta-org}.okta.com/oauth2/v1/userinfo enable_login_token = true use_pkce = true ... ``` **Troubleshooting** * "*Okta 400 Bad Request*" * Check that you used the correct authorizationUrl & clientId. * Check that your application is activated. * "*Okta 400 Bad Request displayed. Your request resulted in an error. The 'redirect\_uri' parameter must be a Login redirect URI in the client app settings*". * Make sure you entered as a Sign-in redirect URI for your application as mentioned in the steps above. * "*refused to connect.*" * Make sure you enabled "IFrame Embedding" as mentioned in the steps above. * "*Could not fetch your auth token.*" * Make sure your tokenUrl is the correct url. ### Onelogin[​](#onelogin "Direct link to Onelogin") **Setup (click to expand)** **Steps:** 1. Follow steps 1 & 2 in [Onelogin's documentation](https://onelogin.service-now.com/support?id=kb_article\&sys_id=143e6c13dbfd0450ca1c400e0b9619d6#add) to add an OpenId Connect (OIDC) application in your Onelogin organization. 2. Make sure the Port host is in the `Redirect URIs`: 1. Go to Applications -> The application you just added -> Configuration. 2. Add `https://app.port.io` as a Redirect URI.
**Configure Grafana with OAuth & Port embedding** Example values are for illustration only The following example is just for illustration purposes and may not reflect the actual URLs and client IDs used in your Onelogin setup. Based on Grafana docs for [JWT Configuration](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/jwt/) & [OAuth Configuration](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/generic-oauth/) ``` [security] ;Required for the embedding allow_embedding = true [auth.jwt] ;Required for the embedding ... jwk_set_url = https://{your-onelogin-org}.onelogin.com/oidc/2/certs expect_claims = {"iss": "https://{your-onelogin-org}/oidc/2"} url_login = true ... [auth.generic_oauth] ;Regular OAuth authentication ... client_id = {CLIENT_ID} client_secret = {CLIENT_SECRET} auth_url = https://{your-onelogin-org}.onelogin.com/oidc/2/auth token_url = https://{your-onelogin-org}.onelogin.com/oidc/2/token api_url = https://{your-onelogin-org}.onelogin.com/oidc/2/me enable_login_token = true use_pkce = true ... ``` **Troubleshoot** * "*unrecognized route or not allowed method*" * Check that you used the correct authorizationUrl. * "*client is invalid*" * Check that you used the correct clientId. * "*redirect\_uri did not match any client's registered redirect\_uris*". * Make sure you entered as a Redirect URI for your application as mentioned in the steps above. * "*Could not fetch your auth token.*" * Make sure your tokenUrl is the correct URL. ### Azure AD[​](#azure-ad "Direct link to Azure AD") **Setup (click to expand)** **Steps:** 1. Follow the [Register an application](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#register-an-application) steps in Azure Documentation to add an application in your Azure subscription. 2. Follow the [Add a redirect URI](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#add-a-redirect-uri) steps in Azure documentation to add `https://app.port.io` as a Redirect URI. 3. Follow the [Configure platform settings](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#configure-platform-settings) to configure your application as `Single-page application`. 4. Add a custom scope to your new application: 1. In your application click on the Expose an API button on the left sidebar. 2. Click on the `Add a scope` button to add a scope that will allow Admins and users to consent `Read User`. ![Azure app registration scope that allows admins and users to consent to Read User access](/assets/images/AzureAdScope-fcc8366e5ac9f8f67dbfcf34d85cd0eb.png) 3. Add the scope you just created under the `Authorization Scope` field in the property in your blueprint inside Port. ``` ... "schema": { "properties": { "ff": { "type": "string", "title": "ff", "format": "url", "spec": "embedded-url", "specAuthentication": { "authorizationUrl": "https://app.com", "tokenUrl": "https://app.com", "clientId": "1234", "authorizationScope": [ "api://xxxx-xxxx-xxxx-xxxx-xxxx/user.read" ] } } } } ... ```
**Configure Grafana with OAuth & Port embedding** Example values are for illustration only The following example is just for illustration purposes and may not reflect the actual URLs and client IDs used in your Azure AD setup. Based on Grafana docs for [JWT Configuration](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/jwt/) & [Azure AD Configuration](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/azuread/) ``` [security] ;Required for the embedding allow_embedding = true [auth.jwt] ;Required for the embedding ... email_claim = preferred_username username_claim = preferred_username jwk_set_url = "https://login.microsoftonline.com/common/discovery/v2.0/keys" expect_claims = {"iss": "https://login.microsoftonline.com/{YOUR_APPLICATION_UUID}/v2.0"} url_login = true ... [auth.azuread] ;Regular Azure AD authentication ... client_id = {CLIENT_ID} client_secret = {CLIENT_SECRET} auth_url = "https://login.microsoftonline.com/{YOUR_APPLICATION_UUID}/oauth2/v2.0/authorize" token_url = "https://login.microsoftonline.com/{YOUR_APPLICATION_UUID}/oauth2/v2.0/token" allowed_domains = "{my-domain}.com" use_pkce = true ... ``` **Troubleshoot** * "*Could not fetch your auth token.*" * Make sure your tokenUrl is the correct URL. --- # Enum Enum is a data type used to define a named set of constant values. ## Common usage[​](#common-usage "Direct link to Common usage") Use enum properties when a value must come from a predefined set, such as: * Service lifecycle: experimental, production, deprecated. * Deployment status: pending, in progress, succeeded, failed. * Environment: development, staging, production. * Service criticality: low, medium, high, critical. ## API definition[​](#api-definition "Direct link to API definition") ### Limit field[​](#limit-field "Direct link to Limit field") When creating a blueprint property of type Enum in the UI, use the **limit** field to define how many values users can select for that property. In the property creation form, the limit field dropdown provides two options: * **1 value**: Only one value can be selected. * **List of values**: Multiple values can be selected. Selecting the **list of values** option will set the property's `type` to `array` in the JSON definition. Limit field restriction The limit field setting, whether it's **1 value** or a **list of values**, is permanent and cannot be changed after the property is created. If you create a property with a single value limit, you won’t be able to change it later to allow multiple values. To change the limit configuration after creation: 1. Create a new property with the **limit** field set to a **list of values**. 2. Use the [migrate blueprint data](/context-lake/ingestion/ingest-data-into-port/other/migrate-data/.md) feature to insert the data to the new property. 3. Delete the old property. 4. Rename the new property to the old property name (optional). * Basic * Array ``` { "myStringEnum": { "title": "My enum", "icon": "My icon", "type": "string", "enum": ["my-option-1", "my-option-2"], "enumColors": { "my-option-1": "red", "my-option-2": "green" } } } ``` ``` { "myStringArray": { "title": "My enum array", "icon": "My icon", "description": "My enum array", "type": "array", "items": { "type": "string", "enum": ["my-option-1", "my-option-2"], "enumColors": { "my-option-1": "red", "my-option-2": "green" } } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myEnumProp" = { title = "My Enum" required = false enum = ["my-option-1", "my-option-2"] enum_colors = { "my-option-1" = "green" "my-option-2" = "blue" } } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { my_enum_array_prop = { title = "myEnumArrayProp" string_items = { enum = ["my-option-1", "my-option-2", "my-option-3"] enum_colors = { "my-option-1" = "gold" "my-option-2" = "bronze" "my-option-3" = "lightGray" } } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic * Array - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties={ "string_props": { "myEnumProp": { "title": "My Enum", "required": True, "enum_colors": { "my-option-1": "red", "my-option-2": "green", }, "enums": ["my-option-1", "my-option-2"] } } }, relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myEnumProp: { title: "My Enum", required: true, enums: ["my-option-1", "my-option-2"], enumColors: { "my-option-1": "red", "my-option-2": "green", }, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myEnumProp: { title: "My Enum", required: true, enums: ["my-option-1", "my-option-2"], enumColors: { "my-option-1": "red", "my-option-2": "green", }, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myEnumProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My Enum"), Required: pulumi.Bool(false), Enums: pulumi.StringArray{ pulumi.String("my-option-1"), pulumi.String("my-option-2"), }, EnumColors: pulumi.StringMap{ "my-option-1": pulumi.String("red"), "my-option-2": pulumi.String("green"), }, }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="myBlueprint", properties={ "array_props": { "myEnumProp": { "title":"My Enum Array", "required":True, "string_items": { "enums" : ["my-option-1", "my-option-2"], "enum_colors" : { "my-option-1": "red", "my-option-2": "green", } } } } }, relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { arrayProps: { myEnum: { title: "My enum", required: true, stringItems: { enumColors: { "my-option-1": "red", "my-option-2": "green", }, enums: ["my-option-1", "my-option-2"], }, } } }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { arrayProps: { myEnumProp: { title: "My Enum", required: true, stringItems: { enums: ["my-option-1", "my-option-2"], enumColors: { "my-option-1": "red", "my-option-2": "green", }, }, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("myBlueprint"), Properties: &port.BlueprintPropertiesArgs{ ArrayProps: port.BlueprintPropertiesArrayPropsMap{ "myEnumProp": &port.BlueprintPropertiesArrayPropsArgs{ Title: pulumi.String("My Enum"), Required: pulumi.Bool(false), StringItems: &port.BlueprintPropertiesArrayPropsStringItemsArgs{ Enums: pulumi.StringArray{ pulumi.String("my-option-1"), pulumi.String("my-option-2"), }, EnumColors: pulumi.StringMap{ "my-option-1": pulumi.String("red"), "my-option-2": pulumi.String("green"), }, }, }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## Available enum colors[​](#available-enum-colors "Direct link to Available enum colors") Properties defined using [enum](#api-definition) can also include specific colors for the different values available in the property definition, the available enum colors are: ``` blue turquoise orange purple pink yellow green red darkGray lightGray bronze ``` --- # Labeled URL Labeled URL is an object type used to store URLs with custom display labels. This property allows you to associate a human-readable label with a URL, making it easier to display meaningful link text in the UI instead of showing icons only. Creating via UI When creating a [URL property](/context-lake/data-model/setup-blueprint/properties/url/.md) in the UI, enabling the **Custom display text** toggle creates a labeled URL property. Note that this toggle cannot be changed after the property is saved. ## Common usage[​](#common-usage "Direct link to Common usage") Use labeled URL properties when a link needs readable display text, such as: * Runbook links labeled with incident types. * Monitoring dashboards labeled with environment names. * Pull requests labeled with their titles. ## Schema structure[​](#schema-structure "Direct link to Schema structure") A labeled URL object contains two fields: | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------ | | `url` | string | Yes | The URL to link to (internal or external). | | `displayText` | string | No | The display text to show for the link. | ### Example value[​](#example-value "Direct link to Example value") ``` { "url": "https://app.datadoghq.com/dashboard/abc123", "displayText": "Production Metrics Dashboard" } ``` ## Display behavior[​](#display-behavior "Direct link to Display behavior") Labeled URLs are rendered intelligently based on the URL type: * **Internal links**: Displayed as navigable links within Port. * **External links**: Displayed as buttons that open in a new tab. ## API definition[​](#api-definition "Direct link to API definition") * Basic * Array ``` { "myLabeledUrlProp": { "title": "My labeled URL", "icon": "Link", "description": "My labeled URL property", "type": "object", "format": "labeled-url" } } ``` ``` { "myLabeledUrlArray": { "title": "My labeled URL array", "icon": "Link", "description": "My labeled URL array", "type": "array", "items": { "type": "object", "format": "labeled-url" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { object_props = { "myLabeledUrlProp" = { title = "My labeled URL" description = "My labeled URL property" format = "labeled-url" } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myLabeledUrlArray" = { title = "My labeled URL array" description = "My labeled URL array" object_items = { format = "labeled-url" } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic * Array - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesObjectPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( object_props={ "myLabeledUrlProp": BlueprintPropertiesObjectPropsArgs( title="My labeled URL", required=False, format="labeled-url", ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { objectProps: { myLabeledUrlProp: { title: "My labeled URL", required: false, format: "labeled-url", }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { objectProps: { myLabeledUrlProp: { title: "My labeled URL", required: false, format: "labeled-url", }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ ObjectProps: port.BlueprintPropertiesObjectPropsMap{ "myLabeledUrlProp": port.BlueprintPropertiesObjectPropsArgs{ Title: pulumi.String("My labeled URL"), Required: pulumi.Bool(false), Format: pulumi.String("labeled-url"), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesArrayPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( array_props={ "myLabeledUrlArray": BlueprintPropertiesArrayPropsArgs( title="My labeled URL array", required=False, object_items={ "format": "labeled-url" } ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { arrayProps: { myLabeledUrlArray: { title: "My labeled URL array", required: false, objectItems: { format: "labeled-url", }, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { arrayProps: { myLabeledUrlArray: { title: "My labeled URL array", required: false, objectItems: { format: "labeled-url", }, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ ArrayProps: port.BlueprintPropertiesArrayPropsMap{ "myLabeledUrlArray": port.BlueprintPropertiesArrayPropsArgs{ Title: pulumi.String("My labeled URL array"), Required: pulumi.Bool(false), ObjectItems: port.BlueprintPropertiesArrayPropsObjectItemsArgs{ Format: pulumi.String("labeled-url"), }, }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## Using labeled URLs in self-service actions[​](#using-labeled-urls-in-self-service-actions "Direct link to Using labeled URLs in self-service actions") You can use labeled URL properties as user inputs in self-service actions and automations. The input form provides a JSON editor with schema validation to ensure proper structure. ### Example action input[​](#example-action-input "Direct link to Example action input") ``` { "documentationLink": { "title": "Documentation Link", "description": "Link to the service documentation", "type": "object", "format": "labeled-url" } } ``` ### Example entity data[​](#example-entity-data "Direct link to Example entity data") When populating an entity with labeled URL data: ``` { "identifier": "my-service", "title": "My Service", "properties": { "documentationLink": { "url": "https://docs.example.com/my-service", "label": "Service Documentation" }, "relatedLinks": [ { "url": "https://github.com/org/my-service", "label": "GitHub Repository" }, { "url": "https://app.datadoghq.com/dashboard/xyz", "label": "Monitoring Dashboard" } ] } } ``` --- # Markdown The `Markdown` property is used to display [Markdown](https://en.wikipedia.org/wiki/Markdown) content within an [entity]() in Port.
Using this property will automatically create an additional tab in each [entity page](/interface-builder/port-interface/page/entity-page/.md), displaying the formatted content. ## Common usage[​](#common-usage "Direct link to Common usage") Use Markdown properties to display documentation alongside your entities, such as: * Repository README files. * Service runbooks and troubleshooting instructions. * Architecture notes and Mermaid diagrams. ## Schema definition[​](#schema-definition "Direct link to Schema definition") ``` { "title": "Markdown Property", "type": "string", "format": "markdown", "description": "A Markdown property" } ``` ## Upload images[​](#upload-images "Direct link to Upload images") You can add images directly to your content while editing a **markdown property** in Port. Use any of the following methods to upload an image: * **Paste from clipboard**: copy an image and paste it into the editor with `Cmd+V` (macOS) or `Ctrl+V` (Windows/Linux). * **Drag & drop**: drag an image file from your computer into the editor. * **Toolbar**: click the **image** button in the editor toolbar to select a file to upload. To insert an image hosted at an external URL, use `Ctrl+K` and provide the image URL. **Limitations:** * Maximum file size: **10MB** per image. * Maximum **50** uploaded images per markdown property . * Supported formats are PNG, JPEG, GIF, WebP, APNG, and AVIF. ## Limitations[​](#limitations "Direct link to Limitations") ### Relative links are not supported[​](#relative-links-are-not-supported "Direct link to Relative links are not supported") When displaying markdown content in Port (especially content ingested from Git repositories or other external sources), **relative links will not work**. This is because the markdown is rendered in the context of your Port portal, not the original source location. For example, links like `[guide](./docs/guide.md)` or `[readme](../README.md)` from a GitHub README file will not function correctly when displayed in Port. **Solutions:** * Use **absolute URLs** instead: `[guide](https://github.com/your-org/your-repo/blob/main/docs/guide.md)`. * Transform relative links to absolute URLs during ingestion using JQ expressions in your integration mapping. * For links to other entities in your Port catalog, use entity page URLs: `/serviceEntity?identifier=my-service`. ### GitOps and external markdown files[​](#gitops-and-external-markdown-files "Direct link to GitOps and external markdown files") When using GitOps to display an external markdown file using the `markdown` property, changes made to the markdown file will not be reflected in Port until the `port.yml` file is also updated (this triggers a re-ingestion of the entity). One way to handle this is to add a step in your CI workflow that bumps a "version" field in the `port.yml` file whenever specific files in the repository are updated, ensuring re-ingestion of the entity. *** ## Mermaid[​](#mermaid "Direct link to Mermaid") The markdown widget also supports [Mermaid](https://mermaid-js.github.io/mermaid/#/), a diagramming and charting tool that uses text and code to generate diagrams and charts. To use Mermaid with the markdown widget, all you need to do is add mermaid syntax to the markdown property. You can refer to the [Mermaid Examples](https://mermaid.js.org/syntax/examples.html) for more examples and information on how to use it. ![Payment entity Logic tab showing a Mermaid sequence diagram of service interactions](/assets/images/markdownMermaid-27de23bb422e2d22400f08944074eb47.png) **Mermaid diagram example (click to expand)** ```` ```mermaid sequenceDiagram participant ClientService participant PaymentService participant InventoryService participant ShippingService ClientService->>PaymentService: Request Payment PaymentService-->>InventoryService: Update Inventory InventoryService-->>ShippingService: Ship Order ShippingService-->>InventoryService: Confirm Shipment InventoryService-->>PaymentService: Update Payment Status PaymentService-->>ClientService: Return Payment Status ``` ```` --- # Meta-Properties A meta-property is a property that exists on every entity in Port by default. Meta-properties are always referenced using a dollar sign (`$`) before them, this makes it easier to tell if a property is user-defined or a meta-property. The following table lists all of the available meta-properties: | Meta-property | Description | Notes | | ------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- | | `$identifier` | **Unique** Entity identifier, used for API calls, programmatic access and distinguishing between different entities | | | `$title` | A human-readable name for the entity | | | `$team` | The team this entity belongs to | | | `$icon` | The entity's icon | | | `$createdAt` | The entity's creation time | Value is set upon creation and never changes | | `$updatedAt` | The entity's last update time | Value is updated automatically | | `$createdBy` | The user who created the entity | Value is set upon creation and never changes | | `$updatedBy` | The user who last updated the entity | Value is updated automatically | | `$blueprint` | The entity's blueprint | | --- # Mirror property Mirror property allows you to map data from related entities to your entity. Mirror property can be used for blueprints that have [relations defined](/context-lake/data-model/setup-blueprint/relate-blueprints/.md). When two blueprints are connected via a relation, a new set of properties becomes available to entities in the `source` blueprint. Those new properties are called `mirrorProperties`. Mirror properties will appear on the `source` blueprint as an additional key called `mirrorProperties`. It represents additional properties queried from the `target` blueprint (or from other entities further down the connection graph). Mirror properties allow you to map property values from related entities, to `keys` in the `source` blueprint, thus giving you more context and data when viewing an Entity, while not cluttering the output with unnecessary fields. Mirror properties support both [user-defined](/context-lake/data-model/setup-blueprint/properties/overview/.md#supported-properties) properties, and [meta-properties](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md) by using similar syntax. ## Common usage[​](#common-usage "Direct link to Common usage") Use mirror properties to display values from related catalog entities, such as: * A service's owning team name. * A workload's environment type. * A deployment's repository URL. ## API definition[​](#api-definition "Direct link to API definition") The `mirrorProperties` key is a top-level key in a blueprint definition, alongside `identifier`, `title`, and `schema`. * Basic ``` { "mirrorProperties": { "myMirrorProp": { "title": "My mirror property", "path": "myRelation.myProperty" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. Chained relations via path key The `path` key receives a path of chained relations, which lead up to a blueprint property or [meta-property](#meta-property-as-a-mirror-property) ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic ``` resource "port_blueprint" "related" { identifier = "relatedBlueprint" title = "Related blueprint" properties = { string_props = { myProperty = { title = "Source property" } } } } resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" relations = { myRelation = { target = port_blueprint.related.identifier } } mirror_properties = { myMirrorProp = { title = "My mirror property" path = "myRelation.myProperty" } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintMirrorPropertiesArgs related_blueprint = Blueprint( "relatedBlueprint", identifier="relatedBlueprint", title="Related blueprint", properties={"string_props": {"myProperty": {"title": "Source property"}}}, ) blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", relations={"myRelation": {"target": related_blueprint.identifier}}, mirror_properties={ "myMirrorProp": BlueprintMirrorPropertiesArgs( title="My mirror property", path="myRelation.myProperty" ) }, ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; const relatedBlueprint = new port.Blueprint("relatedBlueprint", { identifier: "relatedBlueprint", title: "Related blueprint", properties: { stringProps: { myProperty: { title: "Source property" } } }, }); export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", relations: { myRelation: { target: relatedBlueprint.identifier } }, mirrorProperties: { myMirrorProp: { title: "My mirror property", path: "myRelation.myProperty", }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const relatedBlueprint = new port.Blueprint("relatedBlueprint", { identifier: "relatedBlueprint", title: "Related blueprint", properties: { stringProps: { myProperty: { title: "Source property" } } }, }); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", relations: { myRelation: { target: relatedBlueprint.identifier } }, mirrorProperties: { myMirrorProp: { title: "My mirror property", path: "myRelation.myProperty", }, }, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { relatedBlueprint, err := port.NewBlueprint(ctx, "relatedBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("relatedBlueprint"), Title: pulumi.String("Related blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myProperty": port.BlueprintPropertiesStringPropsArgs{Title: pulumi.String("Source property")}, }, }, }) if err != nil { return err } blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Relations: port.BlueprintRelationsMap{ "myRelation": port.BlueprintRelationsArgs{Target: relatedBlueprint.Identifier}, }, MirrorProperties: port.BlueprintMirrorPropertiesMap{ "myMirrorProp": port.BlueprintMirrorPropertiesArgs{ Title: pulumi.String("My mirror property"), Path: pulumi.String("myRelation.myProperty"), }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## `Meta-property` as a mirror property[​](#meta-property-as-a-mirror-property "Direct link to meta-property-as-a-mirror-property") This is a mirror property created from one of Port's [meta-properties](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md) on the `target` blueprint. In the following example, we create a mirror property called `microserviceName` which is mapped to the `title` meta-property in the `target` blueprint (in this example the name of the relation is `deployment-to-microservice`). Note how the `title` field is referenced using `$title` because it is a meta-property: ``` "microserviceName": { "title": "Microservice Name", "path": "deployment-to-microservice.$title" } ``` ## Nested relation as a mirror property[​](#nested-relation-as-a-mirror-property "Direct link to Nested relation as a mirror property") It is possible to use mirror properties to map properties from blueprints that are not direct descendants of our `source` blueprint. For example, let's assume we have the following Relation chain: `Microservice -> System -> Domain`. We want to map the members of the domain that owns the microservice directly to the `Microservice` entities. The members of the domain are listed in an [array property](/context-lake/data-model/setup-blueprint/properties/array/.md) under the user-defined property `domain_members`. The names of the relations are: * `Microservice -> System`: `system` * `System -> Domain`: `domain` Let's map the squad members using a mirror property called `owningDomainMembers`: ``` "owningDomainMembers": { "title": "Owning Domain Members", "path": "system.domain.domain_members" } ``` --- # Number Number is a primitive data type used to save numeric data. ## Common usage[​](#common-usage "Direct link to Common usage") Use number properties to store measurements and counts ingested from your tools, such as: * Open vulnerability counts. * CPU and memory allocations. * Deployment replica counts. * Monthly cloud costs. ## API definition[​](#api-definition "Direct link to API definition") * Basic * Enum * Array * Enum Array ``` { "myNumberProp": { "title": "My number", "icon": "My icon", "description": "My number property", "type": "number", "default": 7 } } ``` ``` { "myNumberEnum": { "title": "My number enum", "icon": "My icon", "description": "My number enum", "type": "number", "enum": [1, 2, 3, 4] } } ``` ``` { "myNumberArray": { "title": "My number array", "icon": "My icon", "description": "My number array", "type": "array", "items": { "type": "number" } } } ``` ``` { "myNumberArray": { "title": "My number enum array", "icon": "My icon", "description": "My number enum array", "type": "array", "items": { "type": "number", "enum": [1, 2, 3, 4], "enumColors": { "1": "red", "2": "green", "3": "blue" } } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Enum * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { number_props = { "myNumberProp" = { title = "My number" description = "My number property" default = 7 } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { number_props = { "myNumberProp" = { title = "My number" description = "My number property" enum = [1, 2, 3, 4] } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myNumberArray" = { title = "My number array" description = "My number array" number_items = {} } "myNumberArrayWithDefault" = { title = "My number array with default" description = "My number array" number_items = { default = [1, 2, 3, 4] } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesNumberPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( number_props={ "myNumberProp": BlueprintPropertiesNumberPropsArgs( title="My number", required=False, ) }, ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { numberProps: { myNumberProp: { title: "My number", required: false, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { numberProps: { myNumberProp: { title: "My number", required: false, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ NumberProps: port.BlueprintPropertiesNumberPropsMap{ "myNumberProp": port.BlueprintPropertiesNumberPropsArgs{ Title: pulumi.String("My number"), Required: pulumi.Bool(false), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## Validate number[​](#validate-number "Direct link to Validate number") Number validations support the following operators: * `range` Ranges of numbers are specified using a combination of the `minimum` and `maximum` keywords, (or `exclusiveMinimum` and `exclusiveMaximum` for expressing exclusive range). If *x* is the value being validated, the following must hold true: * *x* ≥ `minimum` * *x* > `exclusiveMinimum` * *x* ≤ `maximum` * *x* < `exclusiveMaximum` - Basic - Array - Terraform ``` { "myNumberProp": { "title": "My number", "icon": "My icon", "description": "My number property", "type": "number", "minimum": 0, "maximum": 50 } } ``` ``` { "myNumberArray": { "title": "My number array", "icon": "My icon", "description": "My number array", "type": "array", "items": { "type": "number", "exclusiveMinimum": 0, "exclusiveMaximum": 50 } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { "number_props" = { "myNumberProp" = { title = "My number" icon = "My icon" description = "My number property" minimum = 0 maximum = 50 } } } } ``` --- # JSON JSON is a data type used to save structured data as key-value pairs. ## Common usage[​](#common-usage "Direct link to Common usage") Use JSON properties to store structured data, such as: * Cloud resource tags and Kubernetes labels. * Service configuration values. * Structured metadata from an integration. ## API definition[​](#api-definition "Direct link to API definition") JSON properties use `"type": "object"` without a `format` field. The following example defines the property in your blueprint; `icon`, `description`, and `default` are optional. * Basic * Array ``` { "myObjectProp": { "type": "object", "title": "My object" } } ``` ``` { "myObjectArray": { "title": "My object array", "icon": "My icon", "description": "My object array", "type": "array", "items": { "type": "object" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { object_props = { "myObjectProp" = { title = "My object" icon = "My icon" description = "My object property" default = jsonencode({ "myKey" = "myValue" }) } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { myObjectArray = { title = "My object array" object_items = {} } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesObjectPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( object_props={ "myObjectProp": BlueprintPropertiesObjectPropsArgs( title="My object", required=False ) } ), relations={}, ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { objectProps: { myObjectProp: { title: "My object", required: true, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { objectProps: { myObjectProp: { title: "My object", required: true, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ ObjectProps: port.BlueprintPropertiesObjectPropsMap{ "myObjectProp": port.BlueprintPropertiesObjectPropsArgs{ Title: pulumi.String("My object"), Required: pulumi.Bool(false), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## Validate object[​](#validate-object "Direct link to Validate object") Object validations support the following operators: * `properties` - which keys must appear and what their type should be. * `additionalProperties` - are keys not defined in `properties` allowed and what their type should be. * `patternProperties` - which regex pattern should properties follow JSON schema validations Object validations follow the JSON schema model, refer to the [JSON schema docs](https://json-schema.org/understanding-json-schema/reference/object.html) to learn about all of the available validations * Basic * Array ``` { "myObjectProp": { "title": "My object", "icon": "My icon", "description": "My object property", "type": "object", "properties": { "myRequiredProp": { "type": "number" } }, "patternProperties": { "^S_": { "type": "string" }, "^I_": { "type": "number" } }, "additionalProperties": true } } ``` ``` { "myObjectArray": { "title": "My object array", "icon": "My icon", "description": "My object array", "type": "array", "items": { "type": "object", "properties": { "myRequiredProp": { "type": "number" } }, "patternProperties": { "^S_": { "type": "string" }, "^I_": { "type": "number" } }, "additionalProperties": true } } } ``` --- # Properties Properties are customizable data fields of [blueprints](/context-lake/data-model/setup-blueprint/overview/.md), used to save and display information from external data sources. Default visibility in tables New properties added to an existing blueprint are **hidden by default** in all table views across Port (catalog pages, entity pages, table and entity details widgets). To display the property in a table, click on the ![Manage Properties icon](/img/icons/Properties.svg)![Manage Properties icon](/img/icons/Properties-dark.svg) **Manage Properties** icon and toggle the property on. ## Configure properties in Port[​](#configure-properties-in-port "Direct link to Configure properties in Port") You can create, delete, or edit properties via the Data model page or directly from the software catalog. ### From the Data model page[​](#from-the-data-model-page "Direct link to From the Data model page") **To edit an existing property:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Expand the blueprint you want to edit by double-clicking on it. 3. Under the **Properties** tab, click on the property you want to edit. 4. Make your desired changes to the form, then click **Save**. **To create a new property:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Expand the blueprint you want to edit by double-clicking on it. 3. Click on the **+ New property** button: ![Blueprint schema New property button](/img/software-catalog/customize-integrations/createNewProperty.png)

4. Fill in the form with the desired property details, including the [property type](/context-lake/data-model/setup-blueprint/properties/overview/.md#supported-properties), then click **Create**. 5. Newly added properties are hidden by default across all views. ### From the software catalog[​](#from-the-software-catalog "Direct link to From the software catalog") Each catalog page in your software catalog contains a table with all entities created from a certain blueprint. You can modify the properties of the blueprint directly from this table: 1. Go to the desired page of your [software catalog](https://app.port.io/organization/catalog). 2. In the top-right corner of the table are its filters, click on the **Manage properties** button: ![Catalog table Manage Properties button](/img/software-catalog/customize-integrations/managePropertiesFromCatalog.png)
3. A dropdown will appear showing all of the properties. To modify or delete a property, hover over it and click on the `...` icon: ![Properties dropdown with shown and hidden toggles](/img/software-catalog/customize-integrations/managePropertiesDropdown.png)
**Note** that [meta-properties](/context-lake/data-model/setup-blueprint/properties/meta-properties/.md) are not editable, so you will not see a `...` icon next to them. 4. To create a new property in the blueprint, click on the **+ Property** button at the bottom of the dropdown. Hiding properties You can also hide properties from the table by clicking on the toggle on the right side of the property name. ## Structure[​](#structure "Direct link to Structure") Each blueprint has a `properties` section under its `schema`. Each property is defined as an object with the following structure: ``` { "myProp": { "title": "My property", "icon": "My icon", "description": "My property", "type": "property_type" } } ``` The different components that make up a basic property definition are listed in the following table: | Field | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `title` | Property title. | | `identifier` | Property identifier. (Maximum 100 characters) | | `type` | **Mandatory field.** The data type of the property. | | `icon` | Icon for the property.

See the [full icon list](/context-lake/data-model/setup-blueprint/overview/.md#full-icon-list). | | `description` | Description of the property.
This value is visible to users when hovering on the info icon in the UI. It provides detailed information about the use of a specific property. | | `default` | Default value for this property in case an entity is created without explicitly providing a value. | Property name The name of the property is the key of the property object. For example, in the code block above, the name of the property is `myProp`. ## Change a property's type[​](#change-a-propertys-type "Direct link to Change a property's type") The **type** field setting of a property (**number**, **string**, etc.) is permanent and cannot be changed after the property is created. If you create a **number** property, you won't be able to change it later to a **string**. To change the type configuration after creation: 1. Create a new property with the desired **type**. 2. Use the [migrate blueprint data](/context-lake/ingestion/ingest-data-into-port/other/migrate-data/.md) feature to insert the data to the new property. Ensure you apply the correct conversions if needed (for example, converting numbers to strings). 3. Delete the old property. 4. Rename the new property to the old property name (optional). ## Configure provider examples[​](#configure-provider-examples "Direct link to Configure provider examples") The property pages include resource examples for version **2.29.0** of the [Terraform provider](https://registry.terraform.io/providers/port-labs/port-labs/2.29.0/docs/resources/port_blueprint) and [Pulumi provider](https://www.pulumi.com/registry/packages/port/api-docs/blueprint/). Run each example independently, or adapt its properties into a blueprint you already manage. ### Configure Terraform[​](#configure-terraform "Direct link to Configure Terraform") Set your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) through environment variables: ``` export PORT_CLIENT_ID="" export PORT_CLIENT_SECRET="" ``` Add this provider configuration to your Terraform project alongside the resource example. Use `https://api.us.port.io` for a US-region organization. ``` terraform { required_providers { port = { source = "port-labs/port-labs" version = "2.29.0" } } } provider "port" { base_url = "https://api.port.io" } ``` Run `terraform init`, then `terraform validate` and `terraform plan` to review the proposed changes before applying them. ### Configure Pulumi[​](#configure-pulumi "Direct link to Configure Pulumi") Create a Pulumi project for your chosen language and install the matching Port package: * Python * TypeScript / JavaScript * Go ``` pip install port-pulumi==2.29.0 ``` ``` npm install @port-labs/port@2.29.0 ``` ``` go get github.com/port-labs/pulumi-port/sdk/v2@v2.29.0 ``` Configure your stack with your Port credentials and region: ``` pulumi config set port:clientId "" pulumi config set port:secret "" --secret pulumi config set port:baseUrl "https://api.port.io" ``` Use `https://api.us.port.io` for a US-region organization. Add the resource example to your program and run `pulumi preview` to review the proposed changes. ## Supported properties[​](#supported-properties "Direct link to Supported properties") The sections below follow the property picker's order. The **Object** form offers JSON, YAML, and Protobuf. Labeled URL has its own documentation page; in the UI, create it by enabling **Custom display text** on a **URL** property. You can configure Email through JSON or a provider. ### Essential[​](#essential "Direct link to Essential") * [Owning teams](/context-lake/data-model/setup-blueprint/properties/owning-teams/.md) - adds a relation to the `Team` blueprint and sets the blueprint's ownership. Can be added once per blueprint. * [String](/context-lake/data-model/setup-blueprint/properties/string/.md) - a primitive data type used to save text data. * [Email](/context-lake/data-model/setup-blueprint/properties/email/.md) - a string format used to save email addresses. * [Number](/context-lake/data-model/setup-blueprint/properties/number/.md) - a primitive data type used to save numeric data. * [Boolean](/context-lake/data-model/setup-blueprint/properties/boolean/.md) - a primitive data type with one of two possible values, true and false. * [Object](/context-lake/data-model/setup-blueprint/properties/object/.md) - groups the JSON, YAML, and Protobuf options in the property form. * [JSON](/context-lake/data-model/setup-blueprint/properties/object/.md) - stores structured data using the API type `object`. * [YAML](/context-lake/data-model/setup-blueprint/properties/yaml/.md) - a string format used to save object definitions in YAML. * [Protobuf](/context-lake/data-model/setup-blueprint/properties/proto/.md) - a string format used to save proto definitions. * [Labeled URL](/context-lake/data-model/setup-blueprint/properties/labeled-url-object/.md) - an object format used to store URLs with custom display labels. * [Array](/context-lake/data-model/setup-blueprint/properties/array/.md) - a data type used to save lists of data. Supports [multi source ingestion](/context-lake/data-model/setup-blueprint/properties/array/.md#multi-source-ingestion) to combine values from multiple writers. * [Enum](/context-lake/data-model/setup-blueprint/properties/enum/.md) - a data type used to define a named set of constant values. * [URL](/context-lake/data-model/setup-blueprint/properties/url/.md) - a data type used to save links to websites. * [Date & time](/context-lake/data-model/setup-blueprint/properties/datetime/.md) - a data type used to reference a date and time. ### Relations[​](#relations "Direct link to Relations") * [Mirror](/context-lake/data-model/setup-blueprint/properties/mirror-property/.md) - maps data from related entities onto your entity. * [Relation](/context-lake/data-model/setup-blueprint/relate-blueprints/.md) - connects entities from different blueprints. ### Advanced[​](#advanced "Direct link to Advanced") * [Calculation](/context-lake/data-model/setup-blueprint/properties/calculation-property/.md) - calculates a property's value based on other properties and relations. * [Aggregation](/context-lake/data-model/setup-blueprint/properties/aggregation-property/.md) - calculates metrics based on relations in your catalog, such as counting entities or averaging values. * [Timer](/context-lake/data-model/setup-blueprint/properties/timer/.md) - defines an expiration date or lifespan for an entity. ### Embed[​](#embed "Direct link to Embed") * [Embedded URL](/context-lake/data-model/setup-blueprint/properties/embedded-url/.md) - embeds external content, such as a dashboard or web page, directly on an entity page. * [Swagger UI](/context-lake/data-model/setup-blueprint/properties/swagger/.md) - displays OpenAPI and AsyncAPI specification files within entity pages. * [Markdown](/context-lake/data-model/setup-blueprint/properties/markdown/.md) - displays formatted Markdown content within entity pages. ### Users & teams[​](#users--teams "Direct link to Users & teams") * [Port Team](/context-lake/data-model/setup-blueprint/properties/team/.md) - references teams that exist in Port. * [Port User](/context-lake/data-model/setup-blueprint/properties/user/.md) - references users that exist in Port. --- # Owning teams Owning Teams can be added once per blueprint. It creates a relation to the `Team` blueprint, and sets the ownership of the blueprint's entities accordingly. ## Common usage[​](#common-usage "Direct link to Common usage") Use owning teams to assign responsibility for services, repositories, and infrastructure resources. Port uses this ownership configuration to identify the teams responsible for each entity. For the full ownership model, including the available ownership types and how to sync, assign, and visualize ownership across your catalog, see [manage ownership in Port](/context-lake/business-context/ownership/.md). --- # Protobuf Protobuf is a data type used to save Protocol Buffers definitions. ## Common usage[​](#common-usage "Direct link to Common usage") Use Protobuf properties to store Protocol Buffers definitions from `.proto` files, such as: * Message schemas exchanged between services. * gRPC service and method definitions. ## API definition[​](#api-definition "Direct link to API definition") * Basic * Array ``` { "myProtoProp": { "title": "My Proto", "icon": "My icon", "description": "My proto property", "type": "string", "format": "proto" } } ``` ``` { "myProtoArray": { "title": "My proto array", "icon": "My icon", "description": "My proto array", "type": "array", "items": { "type": "string", "format": "proto" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myProtoProp" = { title = "My proto" required = false format = "proto" } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myProtoArray" = { title = "My proto array" required = false string_items = { format = "proto" } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myProtoProp": BlueprintPropertiesStringPropsArgs( title="My proto", required=False, format="proto", ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myProtoProp: { title: "My proto", required: false, format: "proto", }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myProtoProp: { title: "My proto", required: false, format: "proto", }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myProtoProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My proto"), Required: pulumi.Bool(false), Format: pulumi.String("proto"), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` --- # String String is a primitive data type used to save text data. ## Common usage[​](#common-usage "Direct link to Common usage") Use string properties to store free-form text from your development tools, such as: * Container image tags and package versions. * Git commit hashes. * Repository file paths. * Service descriptions. ## API definition[​](#api-definition "Direct link to API definition") * Basic * Enum * Array * Enum Array ``` { "myStringProp": { "title": "My string", "icon": "My icon", "description": "My string property", "type": "string", "default": "My default" } } ``` ``` { "myStringEnum": { "title": "My string enum", "icon": "My icon", "description": "My string enum", "type": "string", "enum": ["my-option-1", "my-option-2"], "enumColors": { "my-option-1": "red", "my-option-2": "green" } } } ``` ``` { "myStringArray": { "title": "My string array", "icon": "My icon", "description": "My string array", "type": "array", "items": { "type": "string" } } } ``` ``` { "myStringArray": { "title": "My string enum array", "icon": "My icon", "description": "My string enum array", "type": "array", "items": { "type": "string", "enum": ["my-option-1", "my-option-2"], "enumColors": { "my-option-1": "red", "my-option-2": "green" } } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Enum * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myStringProp" = { title = "My string" required = false } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myStringProp" = { title = "My string" required = false enum = ["my-option-1", "my-option-2"] enum_colors = { "my-option-1" = "red" "my-option-2" = "green" } } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myStringArray" = { title = "My string array" string_items = {} # Pass an empty object only sets the type to string } "myStringArrayWithDefault" = { title = "My string array with default" string_items = { default = ["my-default-1", "my-default-2"] } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic * Enum - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myStringProp": BlueprintPropertiesStringPropsArgs( title="My string", required=False ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myStringProp: { title: "My string", required: true, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myStringProp: { title: "My string", required: true, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myStringProp": &port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My String"), Required: pulumi.Bool(true), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myStringProp": { "title": "My String", "required": True, "enums": ["my-option-1", "my-option-2"], "enum_colors": { "my-option-1": "red", "my-option-2": "green" } } } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myStringProp: { title: "My String", required: true, enums: ["my-option-1", "my-option-2"], enumColors: { "my-option-1": "red", "my-option-2": "green", }, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myStringProp: { title: "My String", required: true, enums: ["my-option-1", "my-option-2"], enumColors: { "my-option-1": "red", "my-option-2": "green", }, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myStringProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My String"), Required: pulumi.Bool(false), Enums: pulumi.StringArray{ pulumi.String("my-option-1"), pulumi.String("my-option-2"), }, EnumColors: pulumi.StringMap{ "my-option-1": pulumi.String("red"), "my-option-2": pulumi.String("green"), }, }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## Validate string[​](#validate-string "Direct link to Validate string") String validations support the following operators: * `minLength` - enforce minimal string length. * `maxLength` - enforce maximal string length. * `pattern` - enforce Regex patterns. - Basic - Array - Terraform - Pulumi ``` { "myStringProp": { "title": "My string", "icon": "My icon", "description": "My string property", "type": "string", "minLength": 1, "maxLength": 32, "pattern": "^[a-zA-Z0-9-]*-service$" } } ``` ``` { "myStringArray": { "title": "My string array", "icon": "My icon", "description": "My string array", "type": "array", "items": { "type": "string", "minLength": 1, "maxLength": 32, "pattern": "^[a-zA-Z0-9-]*-service$" } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myStringProp" = { title = "My string" required = false min_length = 1 max_length = 32 pattern = "^[a-zA-Z0-9-]*-service$" } } } } ``` ``` import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My blueprint", properties: { stringProps: { myStringProp: { maxLength: 32, minLength: 1, pattern: "^[a-zA-Z0-9-]*-service$" } } } }); ``` ## Available enum colors[​](#available-enum-colors "Direct link to Available enum colors") Properties defined using [enum](#api-definition) can also include specific colors for the different values available in the property definition, to see the available enum colors refer to the [enum page](/context-lake/data-model/setup-blueprint/properties/enum/.md#available-enum-colors). --- # Swagger UI The `Swagger UI` property is used to import and display [OpenAPI](https://www.openapis.org/) and/or [AsyncAPI](https://www.asyncapi.com/) specification files within an [entity]() in Port. Using this property will automatically create an additional tab in each [entity page](/interface-builder/port-interface/page/entity-page/.md), displaying the specification files in [Swagger UI](https://swagger.io/) format. Within this tab, you will be able to perform HTTP calls to the spec target directly from Port. The following is an example of a `Swagger UI` tab in an entity page: ![Entity page Swagger UI tab with API endpoints](/img/software-catalog/blueprint/swaggerUiExample.png) ## Common usage[​](#common-usage "Direct link to Common usage") Use Swagger UI properties to display API contracts alongside your services, such as: * OpenAPI specifications for HTTP APIs. * AsyncAPI specifications for event-driven services. * API documentation ingested from a repository or specification URL. ## OpenAPI[​](#openapi "Direct link to OpenAPI") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. ### Definition[​](#definition "Direct link to Definition") * URL * JSON * YAML When using the URL format, Port will query the provided URL for the OpenAPI spec and expects a JSON OpenAPI spec CORS configuration When using URL for the `open-api` display, make sure that your server allows cross-origin (CORS) requests from Port: * EU region: `app.port.io` * US region: `app.us.port.io` To serve the OpenAPI spec from an AWS S3 bucket, add a CORS policy to the bucket that allows requests from both `app.port.io` and `app.us.port.io`. Check out the [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html?icmpid=docs_amazons3_console) for more information. * API * Terraform ``` { "myOpenApi": { "title": "My Open API", "type": "string", "format": "url", "spec": "open-api", "description": "Open-API Prop" } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { myOpenApi = { title = "My Open Api" required = false format = "url" spec = "open-api" } } } } ``` When using the JSON type, you will need to provide the full JSON OpenAPI spec as an object to the entity: * API * Terraform ``` { "myOpenApi": { "title": "My Open API", "type": "object", "spec": "open-api", "description": "Open-API Prop" } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { object_props = { myOpenApi = { title = "My Open Api" required = false spec = "open-api" } } } } ``` When using the YAML type, you will need to provide the full YAML OpenAPI spec to the entity: * API * Terraform ``` { "myOpenApi": { "title": "My Open API", "type": "string", "format": "yaml", "spec": "open-api", "description": "Open-API Prop" } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myOpenApi" = { title = "My OpenAPI" required = false format = "yaml" spec = "open-api" } } } } ``` *** ### Example[​](#example "Direct link to Example") Here is how the Swagger tab in the specific entity page appears when an OpenAPI spec is provided: ![Mock Port API specification rendered in the entity OpenAPI tab with action endpoints](/assets/images/openAPI-9f848eb484597a57746134033f6ed32b.png) ## AsyncAPI[​](#asyncapi "Direct link to AsyncAPI") ### Definition[​](#definition-1 "Direct link to Definition") * URL * JSON * YAML When using the URL format, Port will query the provided URL for the AsyncAPI spec and expects a JSON AsyncAPI spec CORS configuration When using URL for the `async-api` display, make sure that your server allows cross-origin (CORS) requests from Port: * EU region: `app.port.io` * US region: `app.us.port.io` To serve the AsyncAPI spec from an AWS S3 bucket, add a CORS policy to the bucket that allows requests from both `app.port.io` and `app.us.port.io`. Check out the [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html?icmpid=docs_amazons3_console) for more information. * API * Terraform ``` { "myAsyncApi": { "title": "My Async API", "type": "string", "format": "url", "spec": "async-api", "description": "async-api Prop" } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { myAsyncApi = { title = "My Async API" required = false format = "url" spec = "async-api" } } } } ``` When using the JSON type, you will need to provide the full JSON AsyncAPI spec as an object to the Entity: * API * Terraform ``` { "myAsyncApi": { "title": "My Async API", "type": "object", "spec": "async-api", "description": "async-api Prop" } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { object_props = { myAsyncApi = { title = "My Async Api" required = false spec = "async-api" } } } } ``` When using the YAML type, you will need to provide the full YAML AsyncAPI spec to the entity: * API * Terraform ``` { "myOpenApi": { "title": "My Async API", "type": "string", "format": "yaml", "spec": "async-api", "description": "Async-API Prop" } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myAsyncApi" = { title = "My AsyncAPI" required = false format = "yaml" spec = "async-api" } } } } ``` *** ### Example[​](#example-1 "Direct link to Example") Here is how the Swagger tab in the specific entity page appears when an AsyncAPI spec is provided: ![Account Service specification rendered in the entity AsyncAPI tab with user signup payload details](/assets/images/asyncAPI-ea731535b96df9d52904a3e4641c0854.png) --- # Team Team is a data type used to reference teams that exist in Port. ## Common usage[​](#common-usage "Direct link to Common usage") Use Port Team properties to reference teams in your organization, such as: * The team responsible for operational support. * Teams that review changes to a service. * Teams that maintain shared infrastructure. To define entity ownership, use [owning teams](/context-lake/data-model/setup-blueprint/properties/owning-teams/.md). A Port Team property alone does not configure ownership. ## API definition[​](#api-definition "Direct link to API definition") ### Limit field[​](#limit-field "Direct link to Limit field") When creating a blueprint property of type Team in the UI, use the **limit** field to define how many values users can select for that property. In the property creation form, the limit field dropdown provides two options: * **1 value**: Only one value can be selected. * **List of values**: Multiple values can be selected. Selecting the **list of values** option will set the property's `type` to `array` in the JSON definition. Limit field restriction The limit field setting, whether it's **1 value** or a **list of values**, is permanent and cannot be changed after the property is created. If you create a property with a single value limit, you won’t be able to change it later to allow multiple values. To change the limit configuration after creation: 1. Create a new property with the **limit** field set to a **list of values**. 2. Use the [migrate blueprint data](/context-lake/ingestion/ingest-data-into-port/other/migrate-data/.md) feature to insert the data to the new property. 3. Delete the old property. 4. Rename the new property to the old property name (optional). * Basic * Array ``` { "myTeamProp": { "title": "My team", "icon": "My icon", "description": "My team property", "type": "string", "format": "team", "default": "my-team" } } ``` ``` { "myTeamArray": { "title": "My team array", "icon": "My icon", "description": "My team array", "type": "array", "items": { "type": "string", "format": "team" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { myTeamProp = { title = "My team" required = false format = "team" } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { myTeamArray = { title = "My team array" required = false string_items = { format = "team" } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myTeamProp": BlueprintPropertiesStringPropsArgs( title="My team", required=False, format="team", ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myTeamProp: { title: "My team", required: false, format: "team", }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myTeamProp: { title: "My team", required: false, format: "team", }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myTeamProp": &port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My team"), Required: pulumi.Bool(false), Format: pulumi.String("team"), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` --- # Timer Timer is a data type used to define an expiration date/lifespan of a specific entity. ## Common usage[​](#common-usage "Direct link to Common usage") Use timer properties to track deadlines that can trigger automation, such as: * The expiration of a temporary development environment. * The end of temporary resource access. * A scheduled cleanup time for cloud resources. A timer emits a timer-expired event when its deadline passes. Configure an [automation](/workflows/actions-and-automations/define-automations/setup-trigger/.md) to perform cleanup or revoke access; the timer itself does not perform those actions. ## API definition[​](#api-definition "Direct link to API definition") Set the property value on each entity to a future timestamp. Avoid a fixed blueprint default that will eventually be in the past. * Basic ``` { "myTimerProp": { "title": "My timer", "icon": "My icon", "description": "My timer property", "type": "string", "format": "timer" } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myTimerProp" = { title = "My timer" icon = "My icon" description = "My timer property" format = "timer" } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myTimerProp": BlueprintPropertiesStringPropsArgs( title="My timer", format="timer", required=True, ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myTimerProp: { title: "My timer", format: "timer", required: true, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myTimerProp: { title: "My timer", format: "timer", required: true, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myTimerProp": &port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My timer"), Format: pulumi.String("timer"), Required: pulumi.Bool(true), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` ## Example[​](#example "Direct link to Example") Here is an entity for a `timerExample` blueprint which has a timer property with the identifier `timer`. In the example entity, an expiration datetime is specified: ``` "identifier": "entityIdentifier", "title": "Timer Example", "icon": "Microservice", "blueprint": "timerExample", "properties": { "timer": "2022-12-01T16:50:00+02:00" }, "relations": {} ``` Looking at Port's UI, we can see that the timer we created expires in 2 hours: ![TTL example entity with a timer property showing two hours until expiration](/assets/images/TTLCreateEntity-84a96d8dc850e1badfa93d0af64a77cc.png) After 2 hours pass, the property status will change to `Expired`, and an event of `Timer Expired` will be sent to the ChangeLog: ![TTL example entity with its timer property marked as expired](/assets/images/TTLExpiredEntity-d5a7a3279c8b6496d6f5c531e710479d.png) The timer expiration event will also appear in Port's audit log: ![Port audit log showing a successful Timer Expired event for a TTL entity](/assets/images/AuditLogTTL-63da2ea95f7237c21a08e7029309edbd.png) In order to notify about the timer expiration, the following notification body will be sent to the Webhook/Kafka topic configured in the blueprint's `changelogDestination`: ``` { "context": { "blueprint": "timerExample", "entity": "entityIdentifier" }, "action": "TIMER_EXPIRED", "trigger": { "at": "2022-12-01T16:50:00+02:00", "by": { "byPort": true, "orgId": "org_example" }, "origin": "API" }, "resourceType": "entity", "status": "SUCCESS" } ``` --- # URL URL is a data type used to save links to websites. ## Common usage[​](#common-usage "Direct link to Common usage") Use URL properties to link entities to resources in your tools, such as: * Source code repositories and pull requests. * Monitoring dashboards. * Runbooks and service documentation. * CI/CD workflow runs. ## Custom display text[​](#custom-display-text "Direct link to Custom display text") When creating a URL property in the UI, you can enable the **Custom display text** toggle to show custom text rather than the full URL. This is useful when you want to display a human-readable label (such as "Production Dashboard" or "API Documentation") instead of the raw URL. * **Toggle off (default)**: The property stores a simple URL string. * **Toggle on**: The property is defined as a [labeled URL object](/context-lake/data-model/setup-blueprint/properties/labeled-url-object/.md) with both a URL and display text. See the linked page for implementation details. Toggle cannot be modified after saving Once a URL property is saved, the custom display text toggle cannot be changed. ## API definition[​](#api-definition "Direct link to API definition") ### Limit field[​](#limit-field "Direct link to Limit field") When creating a blueprint property of type URL in the UI, use the **limit** field to define how many values users can select for that property. In the property creation form, the limit field dropdown provides two options: * **1 value**: Only one value can be selected. * **List of values**: Multiple values can be selected. Selecting the **list of values** option will set the property's `type` to `array` in the JSON definition. Limit field restriction The limit field setting, whether it's **1 value** or a **list of values**, is permanent and cannot be changed after the property is created. If you create a property with a single value limit, you won’t be able to change it later to allow multiple values. To change the limit configuration after creation: 1. Create a new property with the **limit** field set to a **list of values**. 2. Use the [migrate blueprint data](/context-lake/ingestion/ingest-data-into-port/other/migrate-data/.md) feature to insert the data to the new property. 3. Delete the old property. 4. Rename the new property to the old property name (optional). * Basic * Enum * Array ``` { "myUrlProp": { "title": "My url", "icon": "My icon", "description": "My url property", "type": "string", "format": "url", "default": "https://example.com" } } ``` ``` { "myUrlEnum": { "title": "My url enum", "icon": "My icon", "description": "My url enum", "type": "string", "format": "url", "enum": ["https://example.com", "https://port.io"], "enumColors": { "https://example.com": "red", "https://port.io": "green" } } } ``` ``` { "myUrlArray": { "title": "My url array", "icon": "My icon", "description": "My url array", "type": "array", "items": { "type": "string", "format": "url" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Enum * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myUrlProp" = { title = "My url" required = false format = "url" } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myUrlEnum" = { title = "My url enum" required = false format = "url" enum = ["https://example.com", "https://port.io"] enum_colors = { "https://example.com" = "red", "https://port.io" = "green" } } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myUrlArray" = { title = "My url array" required = false string_items = { format = "url" } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic * Enum - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myUrlProp": BlueprintPropertiesStringPropsArgs( title="My url", required=False, format="url" ), } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myUrlProp: { title: "My url", required: true, format: "url", }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myUrlProp: { title: "My url", required: true, format: "url", }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myUrlProp": &port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My url"), Required: pulumi.Bool(true), Format: pulumi.String("url"), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` * Python * TypeScript * JavaScript * Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint, BlueprintPropertiesArgs, BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myUrlProp":BlueprintPropertiesStringPropsArgs( title="My url", required=True, format="url", enums=["https://example.com", "https://port.io"], enum_colors={ "https://example.com": "red", "https://port.io": "green" } ) }, ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myUrlProp: { title: "My url", required: true, format: "url", enums: ["https://example.com", "https://port.io"], enumColors: { "https://example.com": "red", "https://port.io": "green", }, }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myUrlProp: { title: "My url", required: true, format: "url", enums: ["https://example.com", "https://port.io"], enumColors: { "https://example.com": "red", "https://port.io": "green", }, }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myUrlProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My url"), Required: pulumi.Bool(false), Format: pulumi.String("url"), Enums: pulumi.StringArray{ pulumi.String("https://example.com"), pulumi.String("https://port.io"), }, EnumColors: pulumi.StringMap{ "https://example.com": pulumi.String("red"), "https://port.io": pulumi.String("green"), }, }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` --- # User User is a data type used to reference users that exist in Port. ## Common usage[​](#common-usage "Direct link to Common usage") Use Port User properties to reference people by email address, such as: * The engineer currently on call. * The lead maintainer of a service. * The reviewer assigned to a change. ## Display format[​](#display-format "Direct link to Display format") Both `email` and `user` formats accept an email address, but display it differently: * `email` format displays the raw email string. * `user` format displays the user's name and avatar from Port's list of known users. The `user` format also indicates each user's status: | User status | Example | | ------------ | ------------------------------------------------------------------------------------------------------------------------------- | | Active | ![Active Port user displayed with an initials avatar and email address](/img/software-catalog/blueprint/activeUser.png) | | Invited | ![Invited Port user displayed with a dashed initials avatar and email address](/img/software-catalog/blueprint/invitedUser.png) | | Unregistered | ![Unregistered Port user displayed with a generic avatar and email address](/img/software-catalog/blueprint/externalUser.png) | ## API definition[​](#api-definition "Direct link to API definition") You can add user properties using the blueprint JSON editor, the API, Terraform, or Pulumi. User selection in the UI Selecting **User** in the blueprint UI form creates a [relation](/context-lake/data-model/setup-blueprint/relate-blueprints/.md) to the `_user` blueprint, rather than a user property. ### Single or multiple users[​](#single-or-multiple-users "Direct link to Single or multiple users") Use the **Basic** definition for a single user or the **Array** definition for multiple users. Changing the number of users You cannot switch an existing property between a single value and an array. Create a new property and [migrate its data](/context-lake/ingestion/ingest-data-into-port/other/migrate-data/.md) before replacing the original. * Basic * Array ``` { "myUserProp": { "title": "My user", "icon": "My icon", "description": "My user property", "type": "string", "format": "user", "default": "me@example.com" } } ``` ``` { "myUserArray": { "title": "My user array", "icon": "My icon", "description": "My user array", "type": "array", "items": { "type": "string", "format": "user" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { myUserProp = { title = "My user" required = false format = "user" } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myUserArray" = { title = "My user array" required = false string_items = { format = "user" } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesStringPropsArgs,BlueprintPropertiesArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myUserProp": BlueprintPropertiesStringPropsArgs( title="My user", required=False, format="user", ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myUserProp: { title: "My user", required: false, format: "user", }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myUserProp: { title: "My user", required: false, format: "user", }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myUserProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My user"), Required: pulumi.Bool(false), Format: pulumi.String("user"), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` --- # YAML YAML is a data type used to save object definitions in YAML. ## Common usage[​](#common-usage "Direct link to Common usage") Use YAML properties to store the contents of configuration files, such as: * Kubernetes manifests. * Helm values files. * CI/CD pipeline definitions. ## API definition[​](#api-definition "Direct link to API definition") * Basic * Array ``` { "myYAMLProp": { "title": "My yaml", "icon": "My icon", "description": "My yaml property", "type": "string", "format": "yaml" } } ``` ``` { "myYAMLArray": { "title": "My yaml array", "icon": "My icon", "description": "My yaml array", "type": "array", "items": { "type": "string", "format": "yaml" } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ## Terraform definition[​](#terraform-definition "Direct link to Terraform definition") The examples below use version **2.29.0** of the Terraform and Pulumi providers. [Configure your provider](/context-lake/data-model/setup-blueprint/properties/overview/.md#configure-provider-examples) first, then run one example at a time in its own project. Each example creates a blueprint; use distinct identifiers when you combine examples. * Basic * Array ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { string_props = { "myYAMLProp" = { title = "My yaml" required = false format = "yaml" } } } } ``` ``` resource "port_blueprint" "myBlueprint" { identifier = "myBlueprint" title = "My blueprint" properties = { array_props = { "myYAMLArray" = { title = "My yaml array" required = false string_items = { format = "yaml" } } } } } ``` ## Pulumi definition[​](#pulumi-definition "Direct link to Pulumi definition") * Basic - Python - TypeScript - JavaScript - Go ``` """A Python Pulumi program""" import pulumi from port_pulumi import Blueprint,BlueprintPropertiesArgs,BlueprintPropertiesStringPropsArgs blueprint = Blueprint( "myBlueprint", identifier="myBlueprint", title="My Blueprint", properties=BlueprintPropertiesArgs( string_props={ "myYAMLProp": BlueprintPropertiesStringPropsArgs( title="My yaml", required=False, format="yaml", ) } ), relations={} ) ``` ``` import * as pulumi from "@pulumi/pulumi"; import * as port from "@port-labs/port"; export const blueprint = new port.Blueprint("myBlueprint", { identifier: "myBlueprint", title: "My Blueprint", properties: { stringProps: { myYAMLProp: { title: "My yaml", required: false, format: "yaml", }, }, }, }); ``` ``` "use strict"; const pulumi = require("@pulumi/pulumi"); const port = require("@port-labs/port"); const entity = new port.Blueprint("myBlueprint", { title: "My Blueprint", identifier: "myBlueprint", properties: { stringProps: { myYAMLProp: { title: "My yaml", required: false, format: "yaml", }, }, }, relations: {}, }); exports.title = entity.title; ``` ``` package main import ( "github.com/port-labs/pulumi-port/sdk/v2/go/port" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { blueprint, err := port.NewBlueprint(ctx, "myBlueprint", &port.BlueprintArgs{ Identifier: pulumi.String("myBlueprint"), Title: pulumi.String("My Blueprint"), Properties: port.BlueprintPropertiesArgs{ StringProps: port.BlueprintPropertiesStringPropsMap{ "myYAMLProp": port.BlueprintPropertiesStringPropsArgs{ Title: pulumi.String("My yaml"), Required: pulumi.Bool(false), Format: pulumi.String("yaml"), }, }, }, }) if err != nil { return err } ctx.Export("blueprint", blueprint.Title) return nil }) } ``` --- # Relate Blueprints Relations define connections between [blueprints](/context-lake/data-model/setup-blueprint/overview/.md), consequently connecting the [entities](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md) based on these blueprints.
This provides logical context to the software catalog. ## Common relations[​](#common-relations "Direct link to Common relations") Relations can be used to represent the logical connections between assets in your software catalog, for example: * The **packages** that a **microservice** uses. * The **run** history of a **CI job**. * The **Kubernetes clusters** that exist in a **cloud account**. In this [live demo](https://demo.port.io/settings/data-model) example, we can see the Data model page with all of the blueprints and their relations. 🎬 ## Relation schema structure[​](#relation-schema-structure "Direct link to Relation schema structure") The basic structure of a relation object: ``` { "myRelation": { "title": "My title", "target": "My target blueprint", "required": true, "many": false } } ``` relation declaration A relation exists under the `relations` key in the [Blueprint JSON schema](/context-lake/data-model/setup-blueprint/overview/.md#blueprint-schema-structure). ## Structure table[​](#structure-table "Direct link to Structure table") | Field | Description | Notes | | ------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `identifier` | Unique identifier (Maximum 100 characters) | The identifier is used for API calls, programmatic access and distinguishing between different relations.

The identifier is the key of the relation schema object, in the [schema structure](#relation-schema-structure) above, the identifier is `myRelation` | | `title` | Relation name that will be shown in the UI | Human-readable name for the relation | | `target` | Target blueprint identifier | The target blueprint has to exist when defining the relation | | `required` | Boolean flag to define whether the target must be provided when creating a new entity of the blueprint | | | `many` | Boolean flag to define whether multiple target entities can be mapped to the Relation | For more information refer to [many relation](#many) | ## Types of relations[​](#types-of-relations "Direct link to Types of relations") ### 👤 Single[​](#bust_in_silhouette-single "Direct link to bust_in_silhouette-single") A single type relation is used to map a single target entity to the source. #### 💡 Common Single Relations[​](#-common-single-relations "Direct link to 💡 Common Single Relations") * Map a **Deployment** to the **Running Service** that it deployed. * Map a **package version** to the **package**. * Map a **K8s cluster** to the **cloud account** it is provisioned in. In this [live demo](https://demo.port.io/githubWorkflowEntity?identifier=wish_list_build_185674921\&activeTab=3) example, we can see a specific Deployment Workflow and its related Service. 🎬 #### Single Relation Structure[​](#single-relation-structure "Direct link to Single Relation Structure") A single type relation is distinguished by the `many: false` configuration: * API * Terraform ``` { "myRelation": { "title": "My title", "target": "myTargetBlueprint", "required": false, "many": false } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ``` resource "port_blueprint" "myBlueprint" { # ...blueprint properties # ...user-defined properties relations { identifier = "myRelation" title = "My relation" target = "myTargetBlueprint" required = false many = false } } ``` ### 👥 Many[​](#-many "Direct link to 👥 Many") A many type relation is used to map multiple target entities to the source. #### 💡 Common Many Relations[​](#-common-many-relations "Direct link to 💡 Common Many Relations") * Map dependencies between services. * Map the **packages** used by a **service**. * Map the **cloud resources** used by a **service**. * Map the **services deployed** in a **developer environment**. In this [live demo](https://demo.port.io/jiraIssueEntity?identifier=WISH-789\&activeTab=1) example, we can see a specific Jira issue and its related Services. 🎬 #### Many Relation Structure[​](#many-relation-structure "Direct link to Many Relation Structure") A many type relation is distinguished by the `many: true` configuration: * API * Terraform ``` { "myRelation": { "title": "My title", "target": "myTargetBlueprint", "required": false, "many": true } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ``` resource "port_blueprint" "myBlueprint" { # ...blueprint properties # ...user-defined properties relations { identifier = "myRelation" title = "My relation" target = "myTargetBlueprint" required = false many = true } } ``` Relation Configuration Restriction A Relation can't be configured with both `many` and `required` set to `true`. ## Multi source ingestion[​](#multi-source-ingestion "Direct link to Multi source ingestion") By default, relations use last-writer-wins behavior: each update replaces the full list of targets. Enable **Multi source ingestion** to let multiple [connectors](/context-lake/ingestion/overview/.md) or API callers contribute targets to the same relation without overwriting each other. This is only available on `many` relations. In the [Data model](https://app.port.io/settings/data-model) or software catalog, turn on **Multi source ingestion** when creating the relation. In the API, set `"union": true`. Cannot be changed later This setting cannot be changed after the relation is created. ### Example[​](#example "Direct link to Example") The `service` blueprint has multi source ingestion enabled on its `dependencies` relation. For an entity of that blueprint: 1. **Monday** - the Snyk integration writes `{ "snyk": ["lodash", "axios"] }`. 2. **Tuesday** - Dependabot writes `{ "dependabot": ["axios", "express"] }` without removing Snyk's targets. 3. **Wednesday** - Alice edits the entity in the UI and adds `winston`. Port saves her changes under `user:alice`. A **GET** returns the merged, deduplicated list of targets: ``` { "dependencies": ["lodash", "axios", "express", "winston"] } ``` Each writer only updates its own source key. `axios` appears once even though both scanners reported it. ### Write and read values[​](#write-and-read-values "Direct link to Write and read values") **Write** (`POST`, `PATCH`, `PUT`) - send one source key per update, mapped to an array of target identifiers: ``` { "relations": { "dependencies": { "snyk": ["lodash", "axios"] } } } ``` Use `null` instead of an array to clear a source's slice. Source keys must match `^[A-Za-z0-9._:@/-]{1,128}$`. A source's slice can also be defined using a [search query](/context-lake/ingestion/configure-mapping/configuration-methods/.md#mapping-relations-using-search-queries) instead of a static list of identifiers: ``` { "relations": { "dependencies": { "snyk": { "combinator": "and", "rules": [{ "property": "$blueprint", "operator": "=", "value": "package" }] } } } } ``` **Read** (`GET`, search) - Port returns the merged, deduplicated list of target identifiers: ``` { "dependencies": ["lodash", "axios", "express", "winston"] } ``` In the UI, you edit the assembled list directly. Port stores UI edits under `user:{userId}` automatically. Consistent with array properties and ownership Multi source ingestion works the same way for [array properties](/context-lake/data-model/setup-blueprint/properties/array/.md#multi-source-ingestion) and [owning teams](/context-lake/business-context/ownership/.md#multi-source-ingestion-for-owning-teams): each writer owns a source key, and reads return the deduplicated combination of all sources. ### Constraints[​](#constraints "Direct link to Constraints") * Only available on `many` relations. * Cannot be changed after the relation is created. Create a new relation and migrate the values instead. * Sending a plain array, multiple source keys, or an invalid source key returns `422`. ## Configure relations in Port[​](#configure-relations-in-port "Direct link to Configure relations in Port") Relations are part of the structure of a [blueprint](/context-lake/data-model/setup-blueprint/overview/.md#blueprint-structure). * UI * API * Terraform 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Expand the blueprint from which you would like to create a relation. 3. Click on the `+ New relation` button: ![Repository blueprint with the New relation button and its tooltip](/img/software-catalog/customize-integrations/createRelation.png) 4. Fill in the form with your desired values, then click `Create`. Create in Port ``` { "identifier": "myIdentifier", "title": "My title", "description": "My description", "icon": "My icon", "calculationProperties": {}, "schema": { "properties": {}, "required": [] }, "relations": { "myRelation": { "title": "My title", "target": "My target blueprint", "required": true, "many": false } } } ``` Check out Port's [API reference](/api-reference/port-api/) to learn more. ``` resource "port_blueprint" "myBlueprint" { # ...blueprint properties # ...user-defined properties relations = { "myRelation" = { title = "My title" target = "My target blueprint" required = true many = false } } } ``` --- # Extend the context lake You can extend the Context Lake beyond native integrations by connecting external MCP servers and publishing skills. ## Extension options[​](#extension-options "Direct link to Extension options") * **[MCP connectors](/agent-management/ai-registry/mcp-connectors/.md)** — Connect external MCP servers so Port AI and agents can query tools like Notion, Linear, and Jira at runtime. * **[Skills](/agent-management/ai-registry/skills/overview/.md)** — Publish reusable instruction packages that guide Port AI and agents through domain-specific workflows. --- # Auto discovery The **auto discovery** capability uses [Port AI](/port-ai/overview/.md) to discover entities and their relations. This helps you maintain a complete and accurate catalog, especially for entities that are not automatically created through integrations (see common use-cases below). ### Common use cases * **Services**: Service blueprint centralizes different components of a service like its repository, incidents for example. For that reason, unlike GitHub repositories or PagerDuty services that sync automatically from integrations, services are typically created manually. Auto discovery helps you identify and create these missing services. * **Users**: Discover users from related entities. For instance, if you have GitHub repositories synced, we can analyze pull requests and issues entities to suggest users who contributed to them but do not yet exist in your catalog. ## When to use catalog auto discovery[​](#when-to-use-catalog-auto-discovery "Direct link to When to use catalog auto discovery") This feature is most effective for blueprints that don't have a single clear source of truth. Below are some guidelines to help you decide when to use catalog discovery and when to use classic integration mapping. **When classic data mapping is preferred** Use [integration mapping](/context-lake/ingestion/configure-mapping/overview/.md) when an entity has a clear, single source of truth, for example: * **Repositories**: When a repository is ingested directly from your SCM tool (e.g., GitHub, GitLab), it makes more sense to use integration mapping to create and maintain it. **When catalog auto discovery is preferred** Use catalog discovery when an entity can be created based on multiple relations to different blueprints, without a clear source of truth or a single mapping pattern: * **Services**: A service can be identified through various sources such as Jira projects, Snyk projects, PagerDuty services, Sonar projects, or a combination of these. Since no single integration fully represents the service, catalog discovery can analyze these related entities to suggest services. * **Users**: A user can be derived from multiple relations, including Jira users, Snyk users, PagerDuty users, and more. Catalog discovery helps unify these sources to identify users who should exist in your catalog. ## How to use catalog auto discovery[​](#how-to-use-catalog-auto-discovery "Direct link to How to use catalog auto discovery") **Run the discovery:** 1. Navigate to the [Catalog](https://app.port.io/organization/catalog) page in Port. 2. Open the catalog page of the blueprint for which you want to discover new entities. 3. Click on the ![Port AI icon](/img/icons/AI-icon.svg)![Port AI icon](/img/icons/AI-dark-icon.svg) button in the top right corner of the page. 4. For the best results, we recommend providing the definition of the blueprint you want to discover, along with clear instructions for patterns or specific properties that should be considered. For example: * **Mono-repo microservices:** ``` Services are represented as code in a repository. Check the file structure of each repository to identify services. Services may be found in specific folders, such as "apps" or "services". ``` * **Service repository identification:** ``` Focus on repos that have keywords that can indicate they are services (e.g., "service", "ms", "srv"). Ignore repos of libraries and packages. Having also a PagerDuty service with a similar name as a repo is a strong indication that this is a service. ``` * **Identify users:** ``` Check "Jira issues" assignees and "pull requests" to identify developers in the organization. ``` 5. Select related blueprints to analyze. The entities from these blueprints will be used to identify patterns and suggest new entities for your target blueprint. This field is mandatory and is automatically filled with all directly related blueprints. 6. Click on the `Discover` button. ![Catalog discovery form with instructions and related blueprints selected](/img/software-catalog/pages/catalogDiscoveryForm.png) **Review and edit suggestions:** Once the process is complete, a list of suggested entities will be displayed, divided into two sections: **Create** and **Update**. You can: * Edit individual entity suggestions. * Approve or decline suggestions individually or in bulk. * View the proposed updates to existing entities by clicking the ![View changes icon](/img/icons/Diff-icon.svg)![View changes icon](/img/icons/Diff-dark-icon.svg) button. Suggested entities persist until they are approved or declined. You can close the discovery results window and return to review pending suggestions at any time by accessing the discovery results from the blueprint's catalog page using the ![Port AI icon](/img/icons/AI-icon.svg)![Port AI icon](/img/icons/AI-dark-icon.svg) button. ![Catalog discovery results with entity create and update suggestions](/img/software-catalog/pages/catalogDiscoveryResultsWindow.png)

**Re-run the discovery** You can re-run the discovery process at any time to generate additional or different suggestions. Each discovery run analyzes the current state of your catalog and may produce new suggestions based on newly added entities, updated relationships, or refined patterns. Re-running the discovery does not affect previously approved or declined suggestions. Using auto discovery via the API This feature is also available via API for a more programatic execution of the process. Refer to the [API reference](/api-reference/port-api/) catalog auto discovery for the full list of paths. ## Permissions[​](#permissions "Direct link to Permissions") The permissions are derived from the blueprint permissions.
You can approve suggested entities only if you have write access to the blueprint.
For more information about blueprint permissions, see the [set catalog RBAC](/context-lake/consuming-the-lake/govern-data-access/overview/.md) documentation. To learn more about how Port AI uses your data, see the [security and data controls](/port-ai/security-and-data-controls/.md) documentation. ## Limitations[​](#limitations "Direct link to Limitations") * **Entity evaluation limit**: Discovery evaluates only the 500 most recently added entities from each related blueprint. * **Property truncation**: Only the first 100 characters of each property value are analyzed. Longer content (such as large markdown fields) will be truncated. * **LLM provider**: This feature currently uses Port's LLM and does not support [Bring Your Own LLM (BYOLLM)](/port-ai/overview/.md#bring-your-own-llm-byollm). ## FAQ[​](#faq "Direct link to FAQ") 1Can catalog auto discovery use Bring Your Own LLM? 2Are there usage limits? 3Is the AI trained based on my data? 4How much catalog data does each discovery run evaluate? --- # Common jq use-cases Port uses [JQ](https://jqlang.github.io/jq/manual/) to transform and map data in integration mapping configurations, self-service action backends, and dynamic policies. This page covers the patterns that most commonly cause issues: null returns, silent failures, and type errors. ## Filtering arrays that contain empty strings[​](#filtering-arrays-that-contain-empty-strings "Direct link to Filtering arrays that contain empty strings") The built-in `isNotEmpty` operator returns `true` for `[""]`, an array with one empty string, because the array itself is not empty. To exclude these, combine `isNotEmpty` with an explicit empty-string check in JQ. **Goal:** Filter entities where `entra_group_ids` contains at least one real value (not just `""`). **Wrong: matches `[""]`:** ``` { "property": "entra_group_ids", "operator": "isNotEmpty" } ``` **Correct: excludes `[""]`:** ``` { "combinator": "and", "rules": [ { "property": "entra_group_ids", "operator": "isNotEmpty" }, { "property": "entra_group_ids", "operator": "!=", "value": { "jqQuery": "[\"\"]" } } ] } ``` If you are writing a JQ expression directly (for example in a mapping or backend body), use: ``` .entra_group_ids | map(select(. != "")) | length > 0 ``` ## Parsing a JSON field value[​](#parsing-a-json-field-value "Direct link to Parsing a JSON field value") Some integration API responses include a field whose value is a **JSON-encoded string** - a string that contains serialized JSON. Use `fromjson` to parse it before accessing keys. YAML inputs in self-service actions In self-service actions, inputs declared with `type: "string"` and `format: "yaml"` are **not** raw strings at runtime. Port validates and parses the submitted YAML, then delivers the resulting object to your backend. You can therefore access keys directly without any parsing step: ``` .inputs.raw_config.dataset ``` If you need the object to be forwarded correctly, make sure the invocation method has `omitUserInputs: false` (see [omitUserInputs and omitPayload](#omituserinputs-and-omitpayload-with-the-port-execution-agent)). **Use case:** An API field `metadata` contains a JSON-encoded string such as `"{\"dataset\": \"prod\"}"` and you need to read the `dataset` key. **Input field:** `metadata` - a plain string whose value is JSON. **Wrong: errors because JQ cannot index a string as an object:** ``` .metadata.dataset ``` **Correct: parses the JSON string first:** ``` (.metadata | fromjson).dataset ``` **Null-safe version** (returns `null` instead of erroring when the field is missing or empty): ``` (.metadata // "{}" | fromjson).dataset ``` **Mapping example (integration YAML):** ``` - kind: workspace port: entity: mappings: properties: dataset: '(.metadata | fromjson).dataset' ``` `format: json` in blueprint properties If the source field is always a JSON-encoded string, you can also declare the blueprint property as `type: "object"` and use `format: "json"` in the mapping so Port parses it automatically. See [Object property](https://docs.port.io/context-lake/data-model/setup-blueprint/properties/object). ## Array relations with missing entities[​](#array-relations-with-missing-entities "Direct link to Array relations with missing entities") If you map a relation to an array and one identifier in that array does not exist as an entity in Port, the upsert of the parent entity fails with a validation error. **Problem mapping:** ``` githubTeams: "[.__teams[].id | tostring]" ``` If team `"3178082"` does not exist in Port, the entire repository entity upsert fails. **Option A: exclude known missing IDs:** ``` githubTeams: '([.__teams[].id | tostring] - ["3178082", "8446394"])' ``` **Option B: filter out items with missing IDs (recommended):** ``` githubTeams: "[.__teams[] | select(.id != null) | .id | tostring]" ``` **Option C: filter to only IDs that match a known pattern:** ``` githubTeams: '[.__teams[].id | tostring | select(test("^[0-9]+$"))]' ``` Relations may be empty Filtering out missing IDs means those relations will be absent from the entity. The entity will still be created, and the relation will just be empty. This is usually the correct tradeoff. ## omitUserInputs and omitPayload with the Port execution agent[​](#omituserinputs-and-omitpayload-with-the-port-execution-agent "Direct link to omitUserInputs and omitPayload with the Port execution agent") When you run a self-service action through the [Port execution agent](https://docs.port.io/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/) (GitLab pipeline, GitHub workflow, or `WEBHOOK` with `agent: true`), Port forwards an event payload that your agent maps with `invocations.json` (see [Control the payload](https://docs.port.io/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/control-the-payload/)). In the documented example payload, `payload.properties` can be an **empty object** until user inputs are included in the forwarded message. Port's guides therefore often set **`omitUserInputs`** and **`omitPayload`** to **`false`** on the invocation method whenever the backend or agent mapping needs those fields (for example [Trigger ServiceNow incident](https://docs.port.io/guides/all/interact-with-servicenow/#trigger-a-servicenow-incident) and [Deploy S3 bucket using Crossplane](https://docs.port.io/guides/all/deploy-s3-bucket-crossplane/)). **Symptom:** Your `invocations.json` filter references `.payload.properties.some_input`, but the condition never matches because `payload.properties` is `{}`. **Fix - set both flags to `false` on the invocation method** (GitLab example): ``` { "invocationMethod": { "type": "GITLAB", "projectName": "my-project", "groupName": "my-group", "defaultRef": "main", "agent": true, "omitUserInputs": false, "omitPayload": false } } ``` **GitHub workflow example:** ``` { "invocationMethod": { "type": "GITHUB", "org": "my-org", "repo": "my-repo", "workflow": "my-workflow.yml", "omitUserInputs": false, "omitPayload": false, "reportWorkflowStatus": true } } ``` **Filter expression after fix:** ``` { "enabled": ".payload.properties.target_platform == \"gcp\"" } ``` Check agent mapping errors first If you see `Could not find suitable mapping for the event` in Port agent logs, confirm that `invocations.json` is loaded and that user inputs are present under `.payload.properties` (see the `omitUserInputs` / `omitPayload` settings above). ## Reading a CODEOWNERS file[​](#reading-a-codeowners-file "Direct link to Reading a CODEOWNERS file") For **GitHub Ocean**, Port recommends **`includedFiles`** on the `repository` resource and mapping file text from **`.__includedFiles[""]`**, instead of the legacy **`file://`** prefix (deprecated; see [Enrich entities with file contents](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities#enrich-entities-with-file-contents)). **Recommended: repository `kind` with `includedFiles`:** ``` - kind: repository selector: query: "true" includedFiles: - README.md - CODEOWNERS - .github/CODEOWNERS port: entity: mappings: properties: readme: .__includedFiles["README.md"] codeowners: (.__includedFiles["CODEOWNERS"] // .__includedFiles[".github/CODEOWNERS"]) ``` `includedFiles` resolves paths on the repository default branch, stores each file under `.__includedFiles`, and uses **`null`** when a path is missing. Extend the list for the paths you rely on (for example `.gitlab/CODEOWNERS` on GitLab - see the [GitLab integration](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/) docs). **Alternative: `file` `kind` with a glob path** (one entity per matched file; useful for deep paths such as `**/.github/CODEOWNERS`): ``` - kind: file selector: query: "true" files: - path: "**/.github/CODEOWNERS" organization: my-org port: entity: mappings: properties: codeowners: .content ``` **Parse the first owner from the default rule (`*`):** ``` .codeowners | split("\n")[] | select(startswith("*")) | split(" ")[1] ``` **Return all owners as an array:** ``` [ .codeowners | split("\n")[] | select(startswith("*")) | split(" ")[1:][] | select(. != "") ] ``` ## Mapping GitHub topics to a team relation[​](#mapping-github-topics-to-a-team-relation "Direct link to Mapping GitHub topics to a team relation") If your teams are represented as GitHub repository topics, use `select` with a list of known team slugs to map them: ``` team: >- [ .topics[] | select(. == "devops" or . == "platform" or . == "security") ] | first // null ``` **Dynamic: match any topic that exists as a Port team identifier:** ``` team: >- .topics | map(select(. != null and . != "")) | first // null ``` **Map to an array relation (multiple team owners):** ``` teams: >- [ .topics[] | select(. == "devops" or . == "platform" or . == "security") ] ``` Match team identifiers exactly Keep the team slug in the topic lowercase and hyphen-separated (for example `platform-eng`), and make sure it matches the Port team identifier exactly. Port identifiers are case-sensitive. ## Null-safe navigation[​](#null-safe-navigation "Direct link to Null-safe navigation") **Problem:** `.properties.description` throws if `properties` is `null`. **Use `//` to provide a fallback:** ``` .properties.description // "" ``` **Use `?` to suppress errors on a whole expression:** ``` (.properties.tags[]? | select(. == "production")) // false ``` **Check before mapping (if/else):** ``` if .description != null and .description != "" then .description else "No description provided" end ``` **Safe array iteration (skip nulls):** ``` [.items[]? | select(. != null) | .id] ``` **Common pattern: safe string concat:** ``` ((.firstName // "") + " " + (.lastName // "")) | ltrimstr(" ") | rtrimstr(" ") ``` ## Filtering workflow runs to a specific repository[​](#filtering-workflow-runs-to-a-specific-repository "Direct link to Filtering workflow runs to a specific repository") The GitHub Ocean integration injects `.__repository` (the repo name string) into each workflow run object. Use it in your selector to scope ingestion: ``` - kind: workflow-run selector: query: '.__repository == "my-repo-name"' ``` **Filter to multiple repositories** ``` selector: query: '[.__repository] | inside(["repo-a", "repo-b", "repo-c"])' ``` **Important:** For GitHub Ocean, ingestion is capped at the **latest 100 workflow runs per workflow** (see [migration from GitHub app](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/migration/key-changes#summary-of-key-changes)). Previously ingested runs can be removed on resync depending on your configuration. To retain history, add a **`ttl`** property on your workflow run blueprint and set it in your mapping so older runs are not deleted during resync. --- # Configuration methods Port provides two ways to configure an integration's mapping - the [form editor](#form-editor), a visual, guided experience, and the [YAML editor](#yaml-editor), a code-based configuration. Both editors apply the same [mapping concepts](/context-lake/ingestion/configure-mapping/overview/.md#mapping-concepts). ## Form editor[​](#form-editor "Direct link to Form editor") Limited availability The form editor is currently available for select integrations, with support for all integrations coming soon. The form editor is the default mapping experience. It provides a visual, step-by-step interface for configuring the [mapping concepts](/context-lake/ingestion/configure-mapping/overview/.md#mapping-concepts) above. To access the form editor, click on an integration in your [connectors page](https://app.port.io/settings/data-sources) and navigate to the **Mapping** tab. If AI is enabled for your organization, an **Ask AI** button is available at the top of the editor for mapping assistance. YAML errors may block the form editor If your mapping YAML contains an error, the form editor may be unavailable and the YAML view will be shown first. In that case, hover over the **Edit form** button to see a description of the issue, or use **Test mapping** in the YAML view to identify it. Each integration resource appears as a collapsible strip that you can drag and drop to reorder. Click a strip to expand and configure it. To add a new resource, click `+ Resource` at the bottom, then select a [kind](/context-lake/ingestion/configure-mapping/overview/.md#resources-and-kinds) from the list of all supported kinds for that integration. Each resource has three configuration phases: ### Extraction[​](#extraction "Direct link to Extraction") Configure [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) to shape the API request for this resource. Selectors add parameters to the API request, changing what the API returns. They do not filter the response after it arrives. Click `+ Selector` to see all selectors supported by the selected kind. Hover over a selector to read its description and purpose. ### Transformation[​](#transformation "Direct link to Transformation") Configure the [entity mappings](/context-lake/ingestion/configure-mapping/overview/.md#entity-mappings) for this resource. The left panel contains the mapping configuration, and the right panel shows **Raw data samples** from the integration to help write and validate JQ expressions. **Target blueprint** Select the blueprint to map data into from the **Target Blueprint** dropdown. Click `+ New Blueprint` to create a new blueprint directly from the mapping form - it will be created in Port and automatically selected. **Data filter** Filters which items from the API response are ingested. This corresponds to the [query](/context-lake/ingestion/configure-mapping/overview/.md#query) concept. Set to `true` to ingest all items, or use a JQ expression to filter specific items. **Property mapping** Maps API response fields to blueprint properties. Each row shows a JQ expression on the left and the target Port property on the right. Click the edit icon next to any property to open the JQ expression editor, which displays a raw data sample alongside your expression for real-time validation. If AI is enabled for your organization, the ![Port AI icon](/img/icons/AI-icon.svg)![Port AI icon](/img/icons/AI-dark-icon.svg) icon appears in the top-right corner of the JQ expression editor - click it to describe in natural language what you want to calculate, and AI will generate the JQ expression for you. Click `+ Property` to add more properties from the blueprint. To create a new property on the blueprint directly, click `+ New Property` - it will be added to the blueprint and immediately available for mapping. For more details on properties, see [setup blueprint](/context-lake/data-model/setup-blueprint/overview/.md). **Map by property** To find and update an entity by one of its properties instead of its identifier, click the edit icon next to the **Identifier** row. In the `Edit JQ expression` editor, toggle **Search query** in the top-right corner. This replaces the identifier with a search query rule. For a description of this feature and its limitations, see [Map by property](/context-lake/ingestion/configure-mapping/overview/.md#map-by-property) in the mapping concepts section. **Relation mapping** Maps [relations](/context-lake/ingestion/configure-mapping/overview/.md#mapping-relations) between entities. Click `+ Relation` to add a relation from the blueprint. To create a new relation on the blueprint, click `+ New Relation`. For more details, see [relate blueprints](/context-lake/data-model/setup-blueprint/relate-blueprints/.md). Each relation supports the two approaches described in [Mapping relations](/context-lake/ingestion/configure-mapping/overview/.md#mapping-relations). When editing a relation's JQ expression, toggle **Search query** in the expression editor to switch from a direct identifier reference to a search-based relation. **Items to parse** To create multiple entities from an array field in the API response, click the `...` menu at the bottom-right of the transformation panel and enable **Items to parse**. Three fields will appear above the property mapping section: **Items To Parse**, **Items To Parse Name**, and **Items To Parse Top Level Transform**. For a description of each option, see [Items to parse](/context-lake/ingestion/configure-mapping/overview/.md#items-to-parse) in the mapping concepts section. ### Test[​](#test "Direct link to Test") Click **Test mapping** to run the mapping against the raw data samples and preview the resulting Port entity. The test shows results only for the current resource. If the test fails, see [Common test errors](/context-lake/ingestion/configure-mapping/overview/.md#common-test-errors) for a description of each error and how to resolve it. ### Advanced configuration[​](#advanced-configuration "Direct link to Advanced configuration") The **Advanced Configuration** section at the bottom of the modal exposes the [advanced options](/context-lake/ingestion/configure-mapping/overview/.md#advanced-options) for the integration. Common options include: * **Auto-create related entities** * **Auto-delete related entities** * **Deletion safety limit** See the [advanced options](/context-lake/ingestion/configure-mapping/overview/.md#advanced-options) section for a description of each setting. Additional options may appear depending on the integration - refer to the integration's documentation for the full list of available options. Once you have finished configuring the mapping, click **Save and Resync** to save your changes and trigger a full resync. *** ## YAML editor[​](#yaml-editor "Direct link to YAML editor") To access the YAML editor, click on an integration in your [connectors page](https://app.port.io/settings/data-sources), navigate to the **Mapping** tab, and click `{...} Edit YAML` in the top-right corner. The YAML editor appears in the bottom corner of the modal, with the test result panel on the right. If AI is enabled for your organization, an **Ask AI** button is available in the right corner above the YAML editor for mapping assistance. ### Extraction[​](#extraction-1 "Direct link to Extraction") This section covers the YAML keys used to control which objects are fetched from the integration's API. Some keys use [JQ queries](https://jqlang.github.io/jq/manual/) to filter the ingested data. * The `resources` key is the root of the YAML configuration: ``` resources: - kind: repository ... ```
* The `kind` key is a specifier for the object you wish to map from the tool's API (in this example, a Github repository).
To see which `kinds` are available for mapping, refer to the integration's documentation. In this example, the available kinds are listed in the [Github integration page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/#port-app-configyml-structure). ``` resources: - kind: repository selector: ... ```
* The `selector` block can contain two types of keys: * Integration-specific keys (e.g., `repoSearch`, `branchSearch`) that add parameters to the API request, changing what the API returns. These correspond to the [selector](/context-lake/ingestion/configure-mapping/overview/.md#selector) concept and vary by integration. * The `query` key, a JQ expression that filters the API response after it arrives. This corresponds to the [query](/context-lake/ingestion/configure-mapping/overview/.md#query) concept and is equivalent to the **Data filter** in the form editor. ``` resources: - kind: repository selector: query: "true" # JQ expression - filters objects in the API response # Integration-specific selector parameters, for example: includedFiles: - README.md - CODEOWNERS port: ``` Using a JQ expression, you can define your desired filter conditions. For example, to ingest only repositories with a name starting with `"service"`, use the `query` key like this: ``` query: .name | startswith("service") ```
For an explanation of these concepts, see [Resources and kinds](/context-lake/ingestion/configure-mapping/overview/.md#resources-and-kinds), [Selector](/context-lake/ingestion/configure-mapping/overview/.md#selector), and [Query](/context-lake/ingestion/configure-mapping/overview/.md#query). ### Transformation[​](#transformation-1 "Direct link to Transformation") This section covers the YAML keys used to map API response fields to Port entity properties. The `port.entity.mappings` key contains the section used to map the object fields to Port entities.
Here you can specify the `blueprint` in Port to which the data should be mapped, and which API object will be ingested to each of its properties. To map properties, specify the property identifier as the key inside the `properties` object. ``` resources: - kind: repository selector: query: "true" port: entity: mappings: # Mappings between one GitHub API object to a Port entity. Each value is a JQ query. identifier: ".name" title: ".name" blueprint: '"service"' properties: description: ".description" url: ".html_url" defaultBranch: ".default_branch" ``` To create multiple mappings of the same kind, add another item to the `resources` array: ``` resources: - kind: repository selector: query: "true" port: entity: mappings: # Mappings between one GitHub API object to a Port entity. Each value is a JQ query. identifier: ".name" title: ".name" blueprint: '"service"' properties: description: ".description" url: ".html_url" defaultBranch: ".default_branch" - kind: repository # In this instance repository is mapped again with a different filter selector: query: '.name == "MyRepositoryName"' port: entity: mappings: ... ``` For an explanation of entity mappings, see [Entity mappings](/context-lake/ingestion/configure-mapping/overview/.md#entity-mappings). **Mapping relations** For instance, a `Service` blueprint can be related to a `PagerDuty service` blueprint. After ingesting all of our Services and PagerDuty services, we want to connect each `Service` to its corresponding `PagerDuty service`. **Assigning relations automatically using the mapping configuration** You can define the relation in the mapping file of your connector (e.g. PagerDuty) so that relations are assigned automatically during ingestion. Here's an example: * Go to your [connectors page](https://app.port.io/settings/data-sources) and click on the PagerDuty exporter: * In the bottom left panel, edit the mapping YAML file, and add the following entry to it: ``` - kind: services selector: query: "true" port: entity: mappings: identifier: .name blueprint: '"service"' properties: {} relations: pager_duty_service: .id ``` In this mapping configuration, `pager_duty_service` is the name of the relation identifier, and `.id` is a JQ expression that extracts the identifier of the `PagerDuty service` from the ingested data. When the data is ingested: * If a `service` entity with the same identifier (`.name`) already exists in your catalog, Port will update it, adding the `pager_duty_service` relation and other associated data, such as the on-call property. * If no such `service` exists, Port will create a new `service` entity with that identifier and attach the related `PagerDuty service` and the on-call property. This configuration ensures your catalog stays up to date automatically. You can also customize the relation logic, for example, using search-based relations, depending on your naming conventions or data structure. **Mapping relations using search queries** In the example above we map a relation using a direct reference to the related entity's `identifier`. Port also allows you to use a [search query rule](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) to map relations based on a **property** of the related entity.
This is useful in cases where you don't have the identifier of the related entity, but you do have one of its properties. For example, consider the following scenario:
Say we have a `service` blueprint that has a relation (named `service_owner`) to a `user` blueprint. The `user` blueprint has a property named `github_username`. Now, we want to map the `service_owner` relation based on the `github_username` property of the `user` entity.
To achieve this, we can use the following mapping configuration: ``` - kind: repository selector: query: 'true' port: entity: mappings: identifier: .name title: .name blueprint: '"service"' relations: service_owner: combinator: '"and"' rules: - property: '"github_username"' operator: '"="' value: .owner.login ``` Instead of directly referencing the `user` entity's `identifier`, we use a search query rule to find the `user` entity whose `github_username` property matches the `.owner.login` value returned from GitHub's API. When using a search query rule to map a relation, Port will query all entities of the **relation's target blueprint** (in this case - `user`) and return the one/s that match the rule. **Map by property** In some cases, we may not know the identifier of the entity we want to map to. If that entity has a property that we do know, we can use it to map the data.
This is especially useful when patching entities whose identifiers are not known in advance. Take the following example: * Say we installed Port's PagerDuty integration, and we want to connect each `service` (Git repository) to the relevant `PagerDuty service`. * We can create a property in our `service` blueprint named `pagerduty_service_id`, containing the identifier of the relevant PagerDuty service. * Then, in the `PagerDuty` integration mapping, we can use this property to map each `PagerDuty service` to the relevant `service`. * This way, we would not need to have a separate blueprint for `PagerDuty services`, since the integration maps directly to the `service` blueprint. Mapping by property is done using a [search query rule](/context-lake/consuming-the-lake/search-and-query/structure-and-syntax/.md#rules) in the following format: ``` resources: - kind: services selector: query: 'true' port: entity: mappings: identifier: combinator: '"and"' rules: - operator: '"="' property: '"pagerduty_service_id"' value: .id blueprint: '"service"' properties: oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email ... # Any other properties you want to map ``` In the example above, we search for a `service` entity whose `pagerduty_service_id` property is equal to the `id` of the PagerDuty service, and map data from the PagerDuty service to it. API usage Searching by property can also be used when using Port's API to [create an entity](/api-reference/create-an-entity/). To avoid failures when multiple blueprints share the same property value, add a `$blueprint` rule to your search query to restrict matches to a specific blueprint: ``` combinator: "and" rules: - property: "$blueprint" operator: "=" value: "service" # your other rules... ``` **Items to parse** **`itemsToParse`** The following example iterates over a Jira issue's `comments` array and creates a separate `comment` entity for each comment, using `.item` to reference each element: ``` - kind: issue selector: query: .issueType == 'Bug' port: itemsToParse: .fields.comments entity: mappings: identifier: .item.id blueprint: '"comment"' properties: text: .item.text author: .item.author.name relations: issue: .key ``` In this example, Port will iterate over each comment in the `comments` array and create a separate `comment` entity for each one. The `.item` reference allows you to access properties of the current comment being processed. **`itemsToParseName`** The following example uses `itemsToParseName: 'comment'` to replace the default `.item` reference, avoiding a naming conflict with a top-level `item` key in the API response: ``` - kind: issue selector: query: .issueType == 'Bug' port: itemsToParse: .fields.comments itemsToParseName: 'comment' entity: mappings: identifier: .comment.id blueprint: '"comment"' properties: text: .comment.text author: .comment.author.name relations: issue: .key ``` In this example, we use `comment` instead of `item` to reference each array element, avoiding conflicts with any top-level `item` property in the API response. **`itemsToParseTopLevelTransform`** By default, Port removes the target array specified in `itemsToParse` from the original payload to improve parsing performance. The `itemsToParseTopLevelTransform` flag controls this behavior: * When set to `true` (default): The target array is removed from the payload, and you can only access array items via `.item` (or your custom `itemsToParseName`). * When set to `false`: The target array remains in the payload, allowing you to access both the original array and individual items. **Example with `itemsToParseTopLevelTransform: true` (default):** ``` - kind: issue selector: query: .issueType == 'Bug' port: itemsToParse: .fields.comments itemsToParseTopLevelTransform: true entity: mappings: identifier: .item.id blueprint: '"comment"' properties: text: .item.text issueKey: .key # Can access top-level properties relations: issue: .key ``` **Example with `itemsToParseTopLevelTransform: false`:** ``` - kind: issue selector: query: .issueType == 'Bug' port: itemsToParse: .fields.comments itemsToParseTopLevelTransform: false entity: mappings: identifier: .item.id blueprint: '"comment"' properties: text: .item.text totalComments: .fields.comments | length # Can access the original array issueKey: .key relations: issue: .key ``` When `itemsToParseTopLevelTransform` is `false`, you can access the original array (e.g., `.fields.comments`) in addition to individual items via `.item`. Limitations * The `itemsToParseName` key is not supported in non-Ocean integrations: [Github app](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/), [Kubernetes](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) and [Webhook integrations](/context-lake/ingestion/ingest-data-into-port/webhook/ingest-via-webhooks-overview/.md). * When `itemsToParseName` is enabled, you cannot use the "test mapping" option in Port's UI. The object returned from Jira for which we would apply this mapping might look like this (note the `comments` array): **Example Jira API response (click to expand)** ``` { "url": "https://example.com/issue/1", "status": "Open", "issueType": "Bug", "comments": [ { "id": "123", "text": "This issue is not reproducing" }, { "id": "456", "text": "Great issue!" } ], "assignee": "user1", "reporter": "user2", "creator": "user3", "priority": "High", "created": "2024-03-18T10:00:00Z", "updated": "2024-03-18T12:30:00Z", "key": "ISSUE-1" } ``` ### Test[​](#test-1 "Direct link to Test") The mapping configuration window contains a JQ playground that allows you to test your JQ queries against example responses from the API of the integrated tool. This is useful for validating your queries and ensuring they return the expected results. For integrations based on the [Ocean framework](https://ocean.port.io/integrations-library/), examples will be automatically generated for each resource `kind` in your mapping, based on real data ingested from the tool. You can disable this behavior by setting the `sendRawDataExamples` flag to `false` in the integration's configuration. To test your mapping against the example data, click on the `Test mapping` button in the bottom-right panel. **Manually add test examples** For each resource `kind` in your mapping (in the bottom-left panel), you can add an example in the `Test examples` section.
Click on the `Add kind` button to add an example: ![Test examples Add kind dropdown options](/img/software-catalog/customize-integrations/addTestExample.png) After adding your example, click on the `Test mapping` button in the bottom-right panel to test your mapping against the example data. If the test fails, see [Common test errors](/context-lake/ingestion/configure-mapping/overview/.md#common-test-errors) for a description of each error and how to resolve it. Port's JQ playground In addition to the aforementioned JQ playground, Port provides a general [JQ playground](https://jq.port.io/) where you can test any JSON snippet against JQ expressions with real-time filters and AI-powered assistance. ### Advanced configuration[​](#advanced-configuration-1 "Direct link to Advanced configuration") **Additional options** To use the [advanced options](/context-lake/ingestion/configure-mapping/overview/.md#advanced-options), add them to the root of the mapping configuration: ``` createMissingRelatedEntities: true deleteDependentEntities: true entityDeletionThreshold: 0 resources: - kind: repository ... ``` **Edit an integration's mapping** Once you have configured an integration's mapping to your liking, click the `Resync` button in the bottom-right to save your changes. To edit an integration's mapping using Port's API, you can use the [Patch integration](/api-reference/update-an-integration/) route. Resync via the API To perform a simple resync of an integration via the API without changing its mapping, use the same `Patch integration` route with the integration identifier and an empty body. --- # Clean up stale entities When you remove a resource type from your integration mapping or decommission an integration, the associated entities in Port are not automatically deleted. This documentation explains how to properly clean up stale entities to keep your software catalog accurate and up to date. ## When cleanup is needed[​](#when-cleanup-is-needed "Direct link to When cleanup is needed") Cleanup is typically required when you: * Remove a `kind` from your integration mapping (for example, removing `pull-request` to stop syncing PRs). * Delete an integration entirely. * Rename a blueprint identifier. * Change your mapping in a way that orphans previously synced entities. ## The 3-step cleanup process[​](#the-3-step-cleanup-process "Direct link to The 3-step cleanup process") Removing stale entities requires completing these steps in order. Skipping a step or performing them out of sequence can leave orphaned data in your catalog. ### Step 1: Remove the mapping[​](#step-1-remove-the-mapping "Direct link to Step 1: Remove the mapping") Before deleting any entities, remove the resource mapping from your integration configuration. This prevents the integration from recreating the entities on the next resync. 1. Go to your [connectors page](https://app.getport.io/settings/data-sources). 2. Click on the integration you want to modify. 3. Navigate to the **Mapping** tab. 4. Remove the entire resource block for the `kind` you no longer want to sync. 5. Click **Save & Resync**. For example, if you no longer want to sync pull requests, remove this entire block: ``` - kind: pull-request selector: query: "true" port: entity: mappings: identifier: ".id | tostring" title: ".title" blueprint: '"pullRequest"' properties: # ... properties ``` Order matters Always remove the mapping **before** deleting entities. If you delete entities first, the next integration resync will recreate them. ### Step 2: Delete the entities[​](#step-2-delete-the-entities "Direct link to Step 2: Delete the entities") Once the mapping is removed, delete the stale entities. You can do this through the UI or via the API. * Using the UI * Using the API ## Delete entities one by one 1. Go to the [catalog page](https://app.getport.io/services) for the relevant blueprint. 2. On the entity you want to delete click on the `...` button. 3. Click on **Unregister** button in the toolbar. 4. Confirm the deletion by clicking on **Unregister** again. ## Delete all entities of a blueprint If you need to delete all entities of a specific blueprint: 1. Go to the catalog page for the blueprint. 2. Click the `...` menu in the top-right corner. 3. Select **Delete page**. 4. Click **Delete** to confirm the deletion. ## Delete a single entity ``` curl -X DELETE "https://api.getport.io/v1/blueprints//entities/" \ -H "Authorization: Bearer $PORT_ACCESS_TOKEN" ``` ## Delete multiple entities (up to 100) ``` curl -X POST "https://api.getport.io/v1/blueprints//bulk/entities/delete" \ -H "Authorization: Bearer $PORT_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "entities": ["entity_id_1", "entity_id_2", "entity_id_3"] }' ``` ## Delete all entities of a blueprint To delete all entities belonging to a specific blueprint: ``` curl -X DELETE "https://api.getport.io/v1/blueprints//all-entities" \ -H "Authorization: Bearer $PORT_ACCESS_TOKEN" ``` If the entities have dependent entities (entities that reference them via required relations), add the `delete_dependents` query parameter: ``` curl -X DELETE "https://api.getport.io/v1/blueprints//all-entities?delete_dependents=true" \ -H "Authorization: Bearer $PORT_ACCESS_TOKEN" ``` Irreversible operation Entity deletion is permanent and cannot be undone. Consider [exporting your entities](#export-before-deletion) before deleting them if you might need the data later. ### Step 3: Delete the blueprint (optional)[​](#step-3-delete-the-blueprint-optional "Direct link to Step 3: Delete the blueprint (optional)") If you no longer need the blueprint itself, delete it after all its entities have been removed. 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Find the blueprint you want to delete. 3. Click the `...` menu on the blueprint card. 4. Select **Delete blueprint**. 5. Type **DELETE** and click on `Delete` to confirm the deletion. Blueprint deletion requirements You can only delete a blueprint if: * It has no remaining entities. * No other blueprints have required relations pointing to it. If other blueprints reference this blueprint, update or remove those relations first. When you delete a blueprint, Port also deletes any associated catalog pages that were automatically created for that blueprint. ## Export before deletion[​](#export-before-deletion "Direct link to Export before deletion") Before deleting entities, you may want to export them for backup or audit purposes. * Using the UI * Using the API 1. Go to the catalog page for the blueprint. 2. Click the **Export** button in the toolbar. 3. Choose your export format (CSV or JSON) to download the file. Use the search endpoint to retrieve all entities of a blueprint: ``` curl -X POST "https://api.getport.io/v1/entities/search" \ -H "Authorization: Bearer $PORT_ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "combinator": "and", "rules": [ { "property": "$blueprint", "operator": "=", "value": "" } ] }' > entities_backup.json ``` ## Handle dependent entities[​](#handle-dependent-entities "Direct link to Handle dependent entities") When deleting entities that are referenced by other entities through relations, you have two options: ### Option 1: Auto-delete dependent entities[​](#option-1-auto-delete-dependent-entities "Direct link to Option 1: Auto-delete dependent entities") Use the `delete_dependents` parameter to automatically delete entities that depend on the ones you're deleting: ``` curl -X DELETE "https://api.getport.io/v1/blueprints//all-entities?delete_dependents=true" \ -H "Authorization: Bearer $PORT_ACCESS_TOKEN" ``` ### Option 2: Update relations first[​](#option-2-update-relations-first "Direct link to Option 2: Update relations first") Manually update or remove the relations in dependent entities before deleting: 1. Identify which entities reference the entities you want to delete. 2. Update those entities to remove or change the relation values. 3. Then delete the original entities. ## Mapping configuration options[​](#mapping-configuration-options "Direct link to Mapping configuration options") Port provides configuration options to control automatic entity deletion during integration resyncs. ### deleteDependentEntities[​](#deletedependententities "Direct link to deleteDependentEntities") When enabled, Port automatically deletes dependent entities when their parent entity is removed during a resync. Add this to your mapping configuration: ``` deleteDependentEntities: true resources: - kind: repository # ... ``` This option only controls deletion through required dependent relations. Setting it to `false` does not disable standard reconciliation cleanup and does not preserve an auto-created related entity that Port identifies as stale. For details about entities created by `createMissingRelatedEntities`, including how to preserve them independently, see [auto-created related entities during reconciliation](/context-lake/ingestion/configure-mapping/overview/.md#auto-created-related-entities-during-reconciliation). ### entityDeletionThreshold[​](#entitydeletionthreshold "Direct link to entityDeletionThreshold") Controls whether the integration's deletion mechanism is active. Set to `0` to disable automatic deletion or `1` to enable it: ``` entityDeletionThreshold: 1 resources: - kind: repository # ... ``` ### enableDelete[​](#enabledelete "Direct link to enableDelete") `enableDelete` is an optional boolean on each item in the mapping `resources` list (same level as `kind`, `selector`, and `port`). When cleanup is on, a resync can delete catalog entities that no longer appear in the integration data.
Set `enableDelete` to `false` on a resource if you still want that data synced into Port, but you do **not** want those entities deleted during cleanup. Typical use case: the source occasionally returns an empty list (outage, filter change, partial sync) and you do not want Port to remove everything that was already in the catalog. | Value | Cleanup behavior | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `true` / omitted | Entities of that resource’s blueprint **can be deleted** if they are missing from the resync (default). | | `false` | Entities of that resource’s blueprint are **not deleted** during cleanup. New and updated entities are still written normally. | Cleanup uses the Port **blueprint** from the mapping (`blueprint: '"…"'`), not the integration `kind` name. The flag protects that blueprint. **Conflicting `enableDelete` values on the same blueprint** If **two (or more) resources map to the same static blueprint** and set conflicting `enableDelete` values: | Resource A | Resource B | Same blueprint? | Resync cleanup deletes for that blueprint | | ---------------- | ---------------- | --------------- | -------------------------------------------- | | `false` | `true` / omitted | Yes | **No** | | `false` | `false` | Yes | **No** | | `true` / omitted | `true` / omitted | Yes | **Yes** (if `entityDeletionThreshold` is on) | One `enableDelete: false` is enough. Another resource that maps to the **same** blueprint with `enableDelete: true` does **not** turn deletes back on for that blueprint. Cleanup ownership is per blueprint, not per mapping row Cleanup ownership is per integration + blueprint: Port stores one set of entities for that blueprint from the integration. There is no separate “delete bucket” per `kind` / mapping row when several rows write to the same blueprint. To allow deletes again, set every resource that maps to that blueprint to `enableDelete: true` (or omit the flag), or split them onto different blueprints. **Interaction with `entityDeletionThreshold`** | `entityDeletionThreshold` | `enableDelete` | Resync cleanup deletes for that blueprint | | ------------------------- | ---------------- | ----------------------------------------------------------------------------- | | off (`0`) | any | No | | on (`1`) | `true` / omitted | Yes (unless another resource protects the same blueprint, as described above) | | on (`1`) | `false` | No | `enableDelete: true` does **not** re-enable deletes when `entityDeletionThreshold` is `0`. **Interaction with `deleteDependentEntities`** When any resource has `enableDelete: false` with a static blueprint literal, Port also turns off cascade deletes (`deleteDependentEntities`) for that resync’s cleanup. That way a related parent deleted in the same cleanup run cannot remove a protected blueprint as a dependent. **YAML example** Different blueprints - each resource controls its own blueprint: **Different blueprints example (click to expand)** ``` entityDeletionThreshold: 1 resources: - kind: namespace enableDelete: false # namespace blueprint: no cleanup deletes selector: query: "true" port: entity: mappings: identifier: .metadata.uid title: .metadata.name blueprint: '"namespace"' - kind: application # enableDelete omitted → true → argocdApplication may be cleaned up selector: query: "true" port: entity: mappings: identifier: .metadata.uid title: .metadata.name blueprint: '"argocdApplication"' ``` Same blueprint - protect wins (entities of `service` are **not** deleted): **Same blueprint example (click to expand)** ``` entityDeletionThreshold: 1 resources: - kind: service enableDelete: false selector: query: '.type == "api"' port: entity: mappings: identifier: .id title: .name blueprint: '"service"' - kind: service # true / omitted does NOT override the false above selector: query: '.type == "worker"' port: entity: mappings: identifier: .id title: .name blueprint: '"service"' ``` **Limitations** * Configure `enableDelete` in the **YAML** mapping editor. There is no dedicated form-editor toggle in the current release. * Applies to **resync / reconciliation cleanup** only. Live or webhook-driven deletes are not controlled by this flag. * Protection matches **static** blueprint string literals only (for example `blueprint: '"namespace"'`). Dynamic JQ blueprint expressions are not protected. For related global options, see [Configure mapping - Advanced options](/context-lake/ingestion/configure-mapping/overview/.md#advanced-options). ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Entities reappear after deletion[​](#entities-reappear-after-deletion "Direct link to Entities reappear after deletion") If entities reappear after you delete them, the integration mapping still includes the resource type. Make sure you: 1. Removed the `kind` from the mapping configuration. 2. Saved and resynced the integration. 3. Then deleted the entities. ### Cannot delete blueprint[​](#cannot-delete-blueprint "Direct link to Cannot delete blueprint") If you cannot delete a blueprint, check for: * **Remaining entities**: Delete all entities of the blueprint first. * **Incoming relations**: Other blueprints may have relations pointing to this blueprint. Update or remove those relations first. ### Dependent entities blocking deletion[​](#dependent-entities-blocking-deletion "Direct link to Dependent entities blocking deletion") If you receive an error about dependent entities: * Use the `delete_dependents=true` parameter in your API call. * Or manually remove the relations from dependent entities first. --- # Configure mapping The mapping of an integration's connector defines the ingested data and its destination. It allows you to specify: * **Which data** you wish to ingest from the integrated tool. * **Which properties** in the integration's blueprints will be filled with the ingested data. ## How does mapping work?[​](#how-does-mapping-work "Direct link to How does mapping work?") Integration mapping is configured in the [connectors page](https://app.port.io/settings/data-sources) of your portal, under `Exporters`.
Each integration has its own mapping, which can be edited via a YAML editor or a visual form. To understand how mapping works, let's take a look at an example. After you complete the [onboarding](/getting-started/overview/.md) and connect your Git provider to Port, you will see an exporter entry in your [connectors page](https://app.port.io/settings/data-sources): ![GitHub exporter entry on the Connectors page showing mapped resource types](/img/software-catalog/customize-integrations/mappingExampleEntry.png)

Clicking on this entry will open a modal. Click the **Mapping** tab to view and edit the mapping configuration.
From there, you can click `{...} Edit YAML` to open the YAML editor, or use the form editor to configure the mapping through a visual interface. Note that Port provides default mapping, providing values to the properties defined in the relevant blueprint: ![GitHub integration Mapping tab showing default resource mappings](/img/software-catalog/customize-integrations/mappingExampleGithub.png)

Port provides two ways to configure the mapping: * [Form editor](/context-lake/ingestion/configure-mapping/configuration-methods/.md#form-editor) - a visual, guided experience. * [YAML editor](/context-lake/ingestion/configure-mapping/configuration-methods/.md#yaml-editor) - a code-based configuration. ## Mapping concepts[​](#mapping-concepts "Direct link to Mapping concepts") The following concepts are the building blocks of a mapping configuration. They apply to both editors - understanding them once lets you work fluently in either. ### Resources and kinds[​](#resources-and-kinds "Direct link to Resources and kinds") A resource represents one type of data to fetch and map. Each resource specifies a **kind** - the API object type to fetch (e.g., `repository`, `pull-request`, `issue`). Resources are processed **sequentially from top to bottom**. This means: * The first resource is fetched and ingested before the second one starts. * If you have relations between entities (e.g., pull requests referencing repositories), list the **target blueprint first** so those entities exist before the relation is created. * For large datasets, the order can affect sync duration since each resource must complete before the next begins. A mapping can contain multiple resources of the same kind with different filters, allowing you to map the same API object to different blueprints or with different property values. ### Selector[​](#selector "Direct link to Selector") Selectors belong to the **Extraction** phase. They modify the API request by adding query parameters or other parameters that change what the API returns. Unlike the `query` filter, selectors act on the request - not the response. Available selectors vary per integration, based on the API capabilities of the integrated tool. Using selectors to narrow what the API returns can **reduce sync time** and **help avoid hitting API rate limits**. ### Query[​](#query "Direct link to Query") The `query` field belongs to the **Transformation** phase. It accepts a JQ expression evaluated against each object returned by the API: * If the expression evaluates to `true`, the object is ingested. * If it evaluates to `false`, the object is skipped. Set `query` to `"true"` to ingest all objects, or use a JQ expression to filter specific objects. This field is called **Data filter** in the form editor. In YAML, it appears as `query` inside the `selector` block - but unlike other selector keys, it filters the API response rather than modifying the request. ### Entity mappings[​](#entity-mappings "Direct link to Entity mappings") Entity mappings define how fields from the API response are translated into Port entity properties. For each resource, you specify a target blueprint, how to derive the entity's identifier and title, which API fields map to which blueprint properties, and optionally which relations to populate. The exact structure of entity mappings differs between the two editors - see [Transformation](/context-lake/ingestion/configure-mapping/configuration-methods/.md#transformation) in the form editor section and [Transformation](/context-lake/ingestion/configure-mapping/configuration-methods/.md#transformation-1) in the YAML editor section. JQ syntax for identifiers with hyphens When using JQ to reference identifiers or property names that contain hyphens, you must wrap them in double quotes and use bracket notation. For example, use `.["my-property"]` instead of `.my-property`, which JQ interprets as subtraction. This applies to secrets, properties, and any other identifiers containing hyphens: * Secrets: `.secrets["zendesk-api-token"]` * Properties: `.properties["my-custom-field"]` * Relations: `.relations["parent-service"]` ### Mapping relations[​](#mapping-relations "Direct link to Mapping relations") In Port, [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md) define how [blueprints](/context-lake/data-model/setup-blueprint/overview/.md) are connected to one another. The mapping configuration lets you populate these relations automatically during ingestion. There are two ways to reference a related entity in a mapping: * **Direct identifier reference** - provide the related entity's identifier as a JQ expression. Use this when the API response contains the identifier of the related entity. * **Search query** - find the related entity by matching one of its properties. Use this when you don't have the identifier directly, but know a property value that uniquely identifies it. **Limitations for search query relations** * A ["single type" relation](/context-lake/data-model/setup-blueprint/relate-blueprints/.md#bust_in_silhouette-single) expects a single entity to be returned. A ["many type" relation](/context-lake/data-model/setup-blueprint/relate-blueprints/.md#-many) expects an array. * The maximum number of entities returned is 500. * Calculation properties are not supported. * Mirror properties are supported only for single type relations, one level deep. * Only the `=`, `in`, and `contains` operators are supported. * The search is scoped to the relation's target blueprint - it will not match entities from other blueprints, even if they have a matching property value. **Manually assigning relations in the UI** You can also assign relations manually without touching the mapping configuration: 1. Go to the relevant catalog page (e.g., [services](https://app.port.io/services)). 2. Hover over an entity, click `...`, and select **Edit**. 3. In the relation field, select the related entity from the dropdown, then click **Update**. **Relation values are overwritten on every sync** Every time an integration syncs an entity, the relation value in Port is **replaced** with whatever the mapping returns. Port does not merge or append relation values across syncs or across different integrations. This has two important implications: 1. **If your JQ returns an empty array** (e.g. because a filter excludes all items), Port will clear that relation - removing any previously set values. 2. **If two integrations both write to the same relation**, the last sync to run wins. The other integration's values are overwritten. The mapping expression must therefore return the **complete desired set** of relation identifiers every time it runs. **Workarounds:** * **Merge all sources in one mapping:** Use a single integration that queries all sources and returns the merged set in the JQ expression. * **Manage the relation manually:** Exclude the relation from the integration's mapping entirely by omitting it from the `relations` block, and assign values manually in Port's UI. * **Use an automation to append:** Create a [Port automation](/workflows/actions-and-automations/define-automations/examples/.md#add-to-a-relation-without-overwriting-existing-values) that reacts to entity changes and uses JQ array concatenation to append new values while preserving existing ones. ### Map by property[​](#map-by-property "Direct link to Map by property") In some cases you may not know the identifier of the entity you want to map to, but you do know one of its properties. Instead of a direct identifier, you can use a search query rule in the `identifier`, `team`, or `relation` fields to find and update the entity by that property. Search query mapping is only supported for these three fields. This is useful when you want to map data directly to an existing entity without needing a separate blueprint. For example, a PagerDuty integration can map directly to a `service` entity using a `pagerduty_service_id` property, without a separate `PagerDuty service` blueprint. **Limitations** * The search query must return **exactly one entity** - otherwise the entire request will fail.
To avoid failures from identical property values across multiple blueprints, include an additional rule filtering by `$blueprint`. * If the search query returns no entities, a new entity **will not** be created. * The query is executed on the same blueprint from the request's URL. * Only the `=` and `in` operators are supported. * `Calculation` and `mirror` properties are not supported. API usage Searching by property can also be used when using Port's API to [create an entity](/api-reference/create-an-entity/). ### Items to parse[​](#items-to-parse "Direct link to Items to parse") When the API returns an array that you want to expand into multiple separate Port entities, use items to parse. Port iterates over the array and creates one entity per item. There are three related options: * **`itemsToParse`** - a JQ expression pointing to the array in the API response. Each element in the array becomes a separate entity. * **`itemsToParseName`** - a custom name for referencing each array element. Use this when your API response already has a top-level key that conflicts with the default name. * **`itemsToParseTopLevelTransform`** - controls whether the target array is removed from the payload during iteration. When `true` (default), the array is removed. When `false`, the original array remains accessible alongside individual items. **Limitations** * `itemsToParseName` is not supported in non-Ocean integrations: [Github app](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/), [Kubernetes](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md), and [Webhook integrations](/context-lake/ingestion/ingest-data-into-port/webhook/ingest-via-webhooks-overview/.md). * When `itemsToParseName` is enabled, you cannot use the "test mapping" option in Port's UI. ### Advanced options[​](#advanced-options "Direct link to Advanced options") These options apply globally across all resources in a mapping configuration. In the YAML editor they are set at the root level, in the form editor they appear under **Advanced Configuration**. | UI label | YAML key | Description | | ---------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Auto-create related entities | `createMissingRelatedEntities` | Automatically creates missing related entities during ingestion. Useful when creating an entity whose related entity does not yet exist in Port. | | Auto-delete related entities | `deleteDependentEntities` | Deletes dependent Port entities when a related entity is removed. Required when two blueprints have a required relation and the source entity must be deleted alongside the target. | | Deletion prevention flag | `entityDeletionThreshold` | Accepts 2 values (0 or 1) that controls if the deletion mechanism of the integration would be activated or not (0 means disabled and 1 is enabled) | #### Per-resource delete control[​](#per-resource-delete-control "Direct link to Per-resource delete control") Set on each item under `resources[]` in the YAML mapping (not at the root level): `enableDelete` (optional, default `true`) controls whether resync cleanup can delete this resource's entities: * `true` / omitted: entities can be deleted during resync cleanup. * `false`: entities are synced and updated, but not deleted during resync cleanup. `enableDelete` is scoped by blueprint, not by resource If another resource maps to the same blueprint with `enableDelete: true` (or omits the key), deletes stay off for that blueprint, protect wins. While protect is on, the `deleteDependentEntities` cascade is also off for that cleanup. See [enableDelete](/context-lake/ingestion/configure-mapping/entity-cleanup/.md#enabledelete) for the full behavior and examples. Additional options may be available depending on the integration. Refer to the specific integration's documentation for the full list. Auto-create related entities in protected blueprints The **Auto-create related entities** option cannot be used to create entities in protected blueprints such as `_user` and `_team`. #### Auto-created related entities during reconciliation[​](#auto-created-related-entities-during-reconciliation "Direct link to Auto-created related entities during reconciliation") When `createMissingRelatedEntities` is set to `true`, Port creates a minimal related entity when a mapping references an entity that does not exist. This entity, sometimes called a skeleton entity, uses the referenced identifier for its identifier and title, along with any default blueprint properties. Port associates the skeleton entity with an auto-managed data source derived from the integration. This association makes the entity part of the integration's reconciliation scope. During a full resync, Port protects a skeleton entity while at least one entity successfully ingested in that resync still references it. If the referencing entity fails to ingest and reconciliation continues, the skeleton entity is no longer protected and may be deleted as stale. This risk applies to partial or entity-level failures where reconciliation still runs. Port does not perform stale entity cleanup when reconciliation is aborted. Auto-created related entities can be deleted Setting `deleteDependentEntities` to `false` does not protect skeleton entities from reconciliation cleanup. This option only controls whether Port can delete entities through required dependent relations. It does not disable the direct deletion of an entity that reconciliation identifies as stale. To preserve a related entity independently of an integration: * Ingest it from a separate integration, pipeline, API request, or manual catalog operation before the integration references it. The entity then has a different data source and is not part of this integration's stale entity cleanup unless the integration later manages it directly. * Set `entityDeletionThreshold` to `0` if you need to disable all automatic entity deletion for the integration. This also prevents cleanup of other entities that disappear from the source. Integrations operate as machine identities, so blueprint entity permissions do not restrict their reconciliation operations. Follow the principle of least privilege when granting additional organization-level permissions. Admin permissions broaden what an integration can access, but they are not required for reconciliation to delete entities associated with that integration's data source. ### JQ runtime[​](#jq-runtime "Direct link to JQ runtime") Port's JQ runtime runs in **UTC**. This affects any time-based JQ functions you use in your mapping, such as `now`, `todate`, `strftime`, and `strptime`. When writing JQ expressions that reference the current time or calculate relative durations, assume the server clock is UTC: ``` # Get the current time as a UTC ISO string now | todate # Output: "2024-04-15T10:30:00Z" # Calculate days since last update (assuming .updated_at is an ISO string) (now - (.updated_at | sub("\\.[0-9]+Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime)) / 86400 | floor ``` Local timezone offsets If your source data contains timestamps with a non-UTC offset (e.g. `2024-04-15T12:30:00+02:00`), you must normalize them to UTC before comparing against `now` or using Port's datetime property format. Port stores and displays all datetime values in UTC. ### Common test errors[​](#common-test-errors "Direct link to Common test errors") When running **Test mapping**, you may come across one of the following errors: * **`Missing kind example for {kind}`**
Cause: No example data is available for this resource kind.
Resolution: Add example data for the missing kind. * **`Selector query is false or evaluated to false`**
Cause: The data filter expression returned `false` for the example data.
Resolution: Update your JQ expression or use an example that matches your filter. * **`must NOT have additional properties, additional property: "[property]"`**
Cause: The mapping YAML contains an unrecognized field, often due to a typo.
Resolution: Check for typos in your field names and ensure they match the expected mapping schema. * **`Blueprint with identifier "[identifier]" was not found`**
Cause: The blueprint identifier referenced in the mapping does not exist in your portal.
Resolution: Verify the identifier matches an existing blueprint in your portal. --- # Advanced The following advanced query parameters are available: * Delete Dependents * Create Missing Related Entities The `delete_dependents` query parameter is used to enable the deletion of dependent Port entities. This is useful when you have two blueprints with a required relation, and the target entity in the relation should be deleted. In this scenario, the delete operation will fail if this flag is set to `false`, if the flag is set to `true`, the source entity will be deleted as well. * Available at `DELETE` API endpoint of a specific entity. * Default: `false` (disabled) * Use case: Deletion of dependent Port entities. Must be enabled if you want to delete a target entity (and its source entities) when the entity's blueprint has required relations. The `create_missing_related_entities` parameter is used to enable the creation of missing related Port entities. This is useful when you want to create an entity and its related entity in one call, or if you want to create an entity that its related entity does not exist yet. In both of these scenarios, the resulting *related* entity will be a barebones entity which you can later on update with relevant information as usual. * Available at `POST`, `PUT`, `PATCH` API endpoints of a specific entity. * Default: `false` (disabled) * Use case: Creation of missing related Port entities. Must be enabled, if you want to create a source entity (and its target related entity) even though the target entity doesn't exist. --- # CI/CD By using Port's CI/CD integrations you can always keep your software catalog up to date by making updates directly from your CI/CD. In addition, you can use Port as a source-of-truth and use values saved in Port for configurations, variables and other programmatic resources for your CI/CD pipelines as they are running. ## Available CI/CD integrations[​](#available-cicd-integrations "Direct link to Available CI/CD integrations") ## [🗃GitHub Workflow](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/github-workflow/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/github-workflow/.md) ## [🗃Codefresh Workflow](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/codefresh-workflow-template/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/codefresh-workflow-template/.md) ## [🗃Jenkins Deployment](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/jenkins-deployment/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/jenkins-deployment/.md) ## [🗃CircleCI Deployment](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/circleci-workflow/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/circleci-workflow/.md) ## [🗃Azure Pipelines](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/azure-pipelines/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/azure-pipelines/.md) ## [🗃Gitlab Pipelines](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/gitlab-pipelines/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/gitlab-pipelines/.md) --- # Azure Pipelines Using Azure Pipelines, you can easily create/update and query entities in Port.

![Azure Pipeline task sending catalog data to Port for display in the web app](/assets/images/azure-pipelines-illustration-27b43edbd8daf04f7bcf197a3c6bad54.png) ## 💡 Common Azure Pipelines usage[​](#-common-azure-pipelines-usage "Direct link to 💡 Common Azure Pipelines usage") Port's API allows for easy integration between Port and your Azure Pipeline jobs, for example: * Report the status of a running **CI job**. * Update the software catalog about a new **build version** for a **microservice**. * Get existing **entities**. ## Setup[​](#setup "Direct link to Setup") To interact with Port using Azure Pipelines, you will first need to [define your Port credentials](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-secret-variables?view=azure-devops\&tabs=yaml%2Cbash#secret-variable-in-the-ui) as variables for your pipeline. Then, pass the defined variables to your pipeline script, for example, `Python`: ``` - task: PythonScript@0 env: PORT_CLIENT_ID: $(PORT_CLIENT_ID) # The variable name for your Port clientId PORT_CLIENT_SECRET: $(PORT_CLIENT_SECRET) # The variable name for your Port clientSecret inputs: scriptSource: "filePath" scriptPath: "main.py" ``` Make sure you have an existing [Blueprint](/context-lake/data-model/setup-blueprint/overview/.md) in your Port installation to create/update entities. ## Working with Port's API[​](#working-with-ports-api "Direct link to Working with Port's API") Here is an example snippet showing how to integrate a job that uses Port's API with your existing Azure pipelines using Python: Add the following task to your Azure pipeline: **Azure pipeline YAML (click to expand)** ``` - script: | pip install -r port_requirements.txt - task: PythonScript@0 env: PORT_CLIENT_ID: $(PORT_CLIENT_ID) PORT_CLIENT_SECRET: $(PORT_CLIENT_SECRET) inputs: scriptSource: "filePath" scriptPath: "port.py" ```
Python dependencies required In the following example, we use Python modules which need to be installed. You can use the following `requirements.txt`: **port\_requirements.txt (click to expand)** ``` requests>=2.28.2 ``` * Create/Update * Get Create the following Python script in your repository to create or update Port entities as part of your pipeline: ``` import os import requests import json # These are the credentials passed by the variables of your pipeline to your tasks and in to your env CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } entity_json = { "identifier": "example-entity", "properties": { "myStringProp": "My value", "myNumberProp": 1, "myBooleanProp": true, "myArrayProp": ["myVal1", "myVal2"], "myObjectProp": {"myKey": "myVal", "myExtraKey": "myExtraVal"} } } # request url : {API_URL}/blueprints//entities create_response = requests.post(f'{API_URL}/blueprints/test-blueprint/entities?upsert=true', json=entity_json, headers=headers) print(json.dumps(create_response.json(), indent=4)) ``` Create the following Python script in your repository to get Port entities as part of your pipeline: ``` import os import requests import json # These are the credentials passed by the variables of your pipeline to your tasks and in to your env CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } # request url : {API_URL}/blueprints//entities/ get_response = requests.get(f"{API_URL}/blueprints/test-blueprint/entities/test-entity", headers=headers) print(json.dumps(get_response.json(), indent=4)) ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/azure-pipelines/examples/.md) page for practical examples of working with Port using Azure Pipelines. --- # Examples ## Basic create/update example[​](#basic-createupdate-example "Direct link to Basic create/update example") In this example you will create blueprints for `ciJob` and `image` entities, and a relation between them. Then you will add some Python code to create new entities in Port every time the Azure Pipeline is triggered: **Image blueprint (click to expand)** Create in Port ``` { "identifier": "image", "title": "Image", "icon": "Docker", "schema": { "properties": { "synkHighVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk High Vaulnerabilities" }, "synkMediumVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk Medium Vaulnerabilities" }, "imageTag": { "type": "string", "title": "Image Tag" }, "gitRepoUrl": { "type": "string", "format": "url", "title": "Git Url", "description": "Git Url for the sourcecode" }, "imageRegistry": { "type": "string", "format": "url", "title": "Image Registry", "description": "Docker registry" }, "unitTestCoverage": { "type": "number", "title": "Unit Test coverage (%)", "description": "The unit test coverage pecentage" }, "integTestCoverage": { "type": "number", "title": "Integ Test coverage (%)", "description": "The integration test coverage pecentage" }, "size": { "type": "number", "title": "Image Size (GB)", "description": "The image size in Gigabyte" } }, "required": [] }, "calculationProperties": {} } ``` **CI Job blueprint (including the image relation) (click to expand)** Create in Port ``` { "identifier": "ciJob", "title": "CI Job", "icon": "CICD", "schema": { "properties": { "triggeredBy": { "type": "string", "icon": "TwoUsers", "title": "Triggered By", "description": "The user who triggered the run" }, "commitHash": { "type": "string", "title": "Commit Hash" }, "jobBranch": { "type": "string", "icon": "Git", "format": "url", "title": "Job branch" }, "runLink": { "type": "string", "format": "url", "title": "Action Run Link" } }, "required": [] }, "relations": { "image": { "title": "Job Images", "target": "image", "required": false, "many": true } }, "calculationProperties": {} } ``` After creating the blueprints, you can add a Python script for creating/updating a Port entity: ``` import os import requests import json # Env vars passed by the pipeline variables CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] API_URL = 'https://api.port.io/v1' credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) # use this access token + header for all http requests to Port access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } entity_json = { "identifier": "new-cijob-run", "properties": { "triggeredBy": os.environ['QUEUED_BY'], "commitHash": os.environ['GIT_SHA'], "actionJob": os.environ['JOB_NAME'], "jobLink": os.environ['JOB_URL'] }, "relations": { "image": ["example-image"] } } create_response = requests.post(f'{API_URL}/blueprints/{blueprint_id}/entities?upsert=true&create_missing_related_entities=true', json=entity_json, headers=headers) ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Relation auto-created Please notice that you have also created the `image` relation, and added a related image entity called `example-image`. This is the artifact of the ciJob, and you will update it later. The creation was done using the `create_missing_related_entities=true` flag in the API url, allowing the relation to be created even though the `example-image` entity doesn't exist yet. After adding your new Python script to your repository, add the following code to your Azure pipeline `yml` file to call your script and update/create a new `ciJob` entity: ``` steps: # ... other tasks and steps - script: | pip install -r port_requirements.txt - task: PythonScript@0 env: PORT_CLIENT_ID: $(PORT_CLIENT_ID) PORT_CLIENT_SECRET: $(PORT_CLIENT_SECRET) QUEUED_BY: $(Build.QueuedBy) GIT_SHA: $(Build.SourceVersion) JOB_NAME: $(Build.DefinitionName) JOB_URL: "$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId)" inputs: scriptSource: "filePath" scriptPath: "main.py" ``` ## Basic get example[​](#basic-get-example "Direct link to Basic get example") The following example gets the `new-cijob-run` entity from the previous example, this can be useful if your CI process creates a build artifact and then references some of its data (for example, the run link of the latest `ciJob`). Add the following snippet to your Python code: ``` entity_id = "new-cijob-run" blueprint_id = "ciJob" get_response = requests.get(f"{API_URL}/blueprints/{blueprint_id}/entities/{entity_id}", headers=headers) entity = get_response.json()['entity'] print(f"Image tag is: {entity['properties']['runLink']}") ``` ## Relation example[​](#relation-example "Direct link to Relation example") The following example demonstrates how to update the `example-image` entity that was automatically created when creating the `ciJob` entity shown in the previous example. Add the following snippet to your Python code: ``` image_entity_json = { "identifier": "example-image", "team": [], "properties": { "imageTag": "v1", "synkHighVulnerabilities": "0", "synkMediumVulnerabilities": "0", "gitRepoUrl": "https://github.com/my-org/my-cool-repo", "imageRegistry": "docker.io/cool-image", "size": "0.71", "unitTestCoverage": "20", "unitTestCoverage": "50" }, "relations": {} } create_image_response = requests.post(f'{API_URL}/blueprints/image/entities?upsert=true', json=image_entity_json, headers=headers) ``` That's it! The entity is created or updated and is visible in the UI. --- # CircleCI workflows Using CircleCI workflows, you can easily create/update and query entities in Port.

![CircleCI workflow sending catalog data to Port for display in the web app](/assets/images/circleci-illustration-08476391e6968de3afaf256b27f6ab8c.png) ## 💡 Common CircleCI workflow usage[​](#-common-circleci-workflow-usage "Direct link to 💡 Common CircleCI workflow usage") Port's API allows for easy integration between Port and your CircleCI workflows, for example: * Report the status of a running **CI job**. * Update the software catalog about a new **build version** for a **microservice**. * Get existing **entities**. ## Setup[​](#setup "Direct link to Setup") To interact with Port using Circle CI, you will first need to set up a [CircleCI context](https://circleci.com/docs/contexts/) in order to save your Port credentials, and pass the context to the relevant workflow. ``` workflows: deploy-service: jobs: - report-to-port: context: # the CircleCI context name of the credentials - port ``` Make sure you have an existing [Blueprint](/context-lake/data-model/setup-blueprint/overview/.md) in your Port installation to create/update entities. ## Working with Port's API[​](#working-with-ports-api "Direct link to Working with Port's API") Here is an example snippet showing how to integrate a job that uses Port's API with your existing CircleCI pipelines using Python: Add the following job and workflow to your CircleCI pipeline: **CircleCI Pipeline YAML (click to expand)** ``` jobs: # ... other jobs report-to-port: docker: - image: cimg/python:3.11 environment: API_URL: https://api.port.io steps: - checkout - run: pip install -r port_requirements.txt - run: python get_port_entity.py workflows: # ... other workflows deploy-production-service: jobs: # ... other jobs - report-to-port: context: - port ```
Python dependencies required In the following example, we use Python modules which need to be installed. You can use the following `requirements.txt`: **port\_requirements.txt (click to expand)** ``` requests>=2.28.2 ``` * Create/Update * Get Create the following Python script in your repository to create or update Port entities as part of your pipeline: ``` import os import requests import json # These are the credentials passed by the 'port' context to your environment variables CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } entity_json = { "identifier": "example-entity", "properties": { "myStringProp": "My value", "myNumberProp": 1, "myBooleanProp": true, "myArrayProp": ["myVal1", "myVal2"], "myObjectProp": {"myKey": "myVal", "myExtraKey": "myExtraVal"} } } # request url : {API_URL}/blueprints//entities create_response = requests.post(f'{API_URL}/blueprints/test-blueprint/entities?upsert=true', json=entity_json, headers=headers) print(json.dumps(create_response.json(), indent=4)) ``` Create the following Python script in your repository to get Port entities as part of your pipeline: ``` import os import requests import json # These are the credentials passed by the 'port' context to your environment variables CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } # request url : {API_URL}/blueprints//entities/ get_response = requests.get(f"{API_URL}/blueprints/test-blueprint/entities/test-entity", headers=headers) print(json.dumps(get_response.json(), indent=4)) ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/circleci-workflow/examples/.md) page for practical examples of working with Port using CircleCI. --- # Examples ## Basic create/update example[​](#basic-createupdate-example "Direct link to Basic create/update example") In this example you will create blueprints for `ciJob` and `image` entities, and a relation between them. Then you will add some Python code to create new entities in Port every time the CircleCI workflow is triggered: **Image blueprint (click to expand)** Create in Port ``` { "identifier": "image", "title": "Image", "icon": "Docker", "schema": { "properties": { "synkHighVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk High Vaulnerabilities" }, "synkMediumVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk Medium Vaulnerabilities" }, "imageTag": { "type": "string", "title": "Image Tag" }, "gitRepoUrl": { "type": "string", "format": "url", "title": "Git Url", "description": "Git Url for the sourcecode" }, "imageRegistry": { "type": "string", "format": "url", "title": "Image Registry", "description": "Docker registry" }, "unitTestCoverage": { "type": "number", "title": "Unit Test coverage (%)", "description": "The unit test coverage pecentage" }, "integTestCoverage": { "type": "number", "title": "Integ Test coverage (%)", "description": "The integration test coverage pecentage" }, "size": { "type": "number", "title": "Image Size (GB)", "description": "The image size in Gigabyte" } }, "required": [] }, "calculationProperties": {} } ``` **CI Job blueprint (including the image relation) (click to expand)** Create in Port ``` { "identifier": "ciJob", "title": "CI Job", "icon": "CICD", "schema": { "properties": { "triggeredBy": { "type": "string", "icon": "TwoUsers", "title": "Triggered By", "description": "The user who triggered the run" }, "commitHash": { "type": "string", "title": "Commit Hash" }, "jobBranch": { "type": "string", "icon": "Git", "format": "url", "title": "Job branch" }, "runLink": { "type": "string", "format": "url", "title": "Action Run Link" } }, "required": [] }, "relations": { "image": { "title": "Job Images", "target": "image", "required": false, "many": true } }, "calculationProperties": {} } ``` After creating the blueprint, you can add a Python script for creating/updating a Port entity: ``` import os import requests import json # Env vars passed by the CircleCI context CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] API_URL = 'https://api.port.io/v1' credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) # use this access token + header for all http requests to Port access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } entity_json = { "identifier": "new-cijob-run", "properties": { "triggeredBy": os.environ['CIRCLE_USERNAME'], "commitHash": os.environ['CIRCLE_SHA1'], "actionJob": os.environ['CIRCLE_JOB'], "runLink": os.environ['CIRCLE_BUILD_URL'] }, "relations": { "image": ["example-image"] } } create_response = requests.post(f'{API_URL}/blueprints/{blueprint_id}/entities?upsert=true&create_missing_related_entities=true', json=entity_json, headers=headers) ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Relation auto-created Please notice that you have also created the `image` relation, and added a related image entity called `example-image`. This is the artifact of the ciJob, and you will update it later. The creation was done using the `create_missing_related_entities=true` flag in the API url, allowing the relation to be created even though the `example-image` entity doesn't exist yet. After adding your new Python script to your repository, add the following code to your CircleCI workflow `yml` file to call your script and update/create a new `ciJob` entity: ``` jobs: # ... other jobs report-to-port: docker: - image: cimg/python:3.11 environment: API_URL: https://api.port.io steps: - checkout - run: pip install -r port_requirements.txt - run: python update_deployment_port.py workflows: # ... other workflows deploy-production-service: jobs: # ... other jobs - report-to-port: context: - port ``` ## Basic get example[​](#basic-get-example "Direct link to Basic get example") The following example gets the `new-cijob-run` entity from the previous example, this can be useful if your CI process creates a build artifact and then references some of its data (for example, the run link of the latest `ciJob`). Add the following snippet to your Python code: ``` entity_id = "new-cijob-run" blueprint_id = "ciJob" get_response = requests.get(f"{API_URL}/blueprints/{blueprint_id}/entities/{entity_id}", headers=headers) entity = get_response.json()['entity'] print(f"Run link is: {entity['properties']['runLink']}") ``` ## Relation example[​](#relation-example "Direct link to Relation example") The following example demonstrates how to update the `example-image` entity that was automatically created when creating the `ciJob` entity shown in the previous example. Add the following snippet to your Python code: ``` image_entity_json = { "identifier": "example-image", "team": [], "properties": { "imageTag": "v1", "synkHighVulnerabilities": "0", "synkMediumVulnerabilities": "0", "gitRepoUrl": "https://github.com/my-org/my-cool-repo", "imageRegistry": "docker.io/cool-image", "size": "0.71", "unitTestCoverage": "20", "unitTestCoverage": "50" }, "relations": {} } create_image_response = requests.post(f'{API_URL}/blueprints/image/entities?upsert=true', json=image_entity_json, headers=headers) print(create_image_response.json()) ``` That's it! The entity is created or updated and is visible in the UI. --- # Codefresh Workflow Template Our [Codefresh workflow template](https://github.com/port-labs/port-codefresh-workflow-template) allows you to create/update and query entities in Port directly from your Codefresh workflow templates.

![Codefresh workflow template sending entity updates to the Port software catalog](/assets/images/codefresh-iilustration-b5a841154d46cf4ca638ace24331e91f.png) ## 💡 Codefresh integration usage[​](#-codefresh-integration-usage "Direct link to 💡 Codefresh integration usage") Port's Codefresh workflow template provides a native way to integrate Port with your Codefresh CI workflows, for example: * Report the status of a running **CI job**. * Update the software catalog about a new **build version** for a **microservice**. * Get existing **entities**. ## Installation[​](#installation "Direct link to Installation") To use Port's Codefresh workflow template you need to perform the following steps: 1. Go to the [workflow template repository](https://github.com/port-labs/port-codefresh-workflow-template) in GitHub. 2. Copy the file [`portWorkflowTemplate.yml`](https://github.com/port-labs/port-codefresh-workflow-template/blob/main/portWorkflowTemplate.yml) to one of your codefresh `git sources` and commit it to your git source. 3. Add the required service account, cluster role and role binding to your codefresh runtime namespace by applying the contents of the [`rbac.yml`](https://github.com/port-labs/port-codefresh-workflow-template/blob/main/rbac.yml) file by using the command: `kubectl apply -f rbac.yml -n YOUR_NAMESPACE`. 4. Add the required secret containing your `PORT_CLIENT_ID` and `PORT_CLIENT_SECRET` after encoding them using base64. You can use [`portCredentials.yml`](https://github.com/port-labs/port-codefresh-workflow-template/blob/main/portCredentials.yml) as an example. Skip default-format credentials If you save the CLIENT\_ID and SECRET using the exact format shown in `portCredentials.yml`, you do not need to provide the parameters `PORT_CREDENTIALS_SECRET`, `PORT_CLIENT_ID_KEY` and `PORT_CLIENT_SECRET_KEY` when calling templates from the workflow template. ### Verify[​](#verify "Direct link to Verify") To verify the installation of the workflow template, follow these steps: 1. Go to the Codefresh interface. 2. Under the CI OPS category, click on Workflow Templates. 3. In the search bar, type `port` the workflow template should appear. ## Usage[​](#usage "Direct link to Usage") Port's Codefresh workflow template supports the following methods: * Create/Update catalog entities - invoked with the `upsert-entity` template, receives the identifier and other properties of a new entity or an entity that needs to be updated. * Get catalog entities - invoked with the `get-entity` template, receives the identifier of an existing entity and retrieves it for use in your CI. - Create/Update - Get ``` - name: entity-upsert templateRef: name: port template: entity-upsert arguments: parameters: # Note: if you save the CLIENT_ID and CLIENT_SECRET in the same format shown # in the portCredentials.yml file, there is no need to provide # PORT_CREDENTIALS_SECRET, PORT_CLIENT_ID_KEY, PORT_CLIENT_SECRET_KEY - name: PORT_CREDENTIALS_SECRET value: "port-credentials" - name: PORT_CLIENT_ID_KEY value: "PORT_CLIENT_ID" - name: PORT_CLIENT_SECRET_KEY value: "PORT_CLIENT_SECRET" - name: BLUEPRINT_IDENTIFIER value: "myBlueprint" - name: ENTITY_IDENTIFIER value: "myEntity" - name: ENTITY_TITLE value: "myTitle" - name: ENTITY_PROPERTIES value: | { "myStringProp": "My value", "myNumberProp": 1, "myBooleanProp": true, "myArrayProp": ["myVal1", "myVal2"], "myObjectProp": {"myKey": "myVal", "myExtraKey": "myExtraVal"} } ``` **Inputs** | Input | Description | Notes | | ------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------- | | `PORT_CREDENTIALS_SECRET` | Name of the secret to get the `CLIENT_ID` and `CLIENT_SECRET` from | Default value: `port-credentials` | | `PORT_CLIENT_ID_KEY` | Key in the secret where the base64 encoded `PORT_CLIENT_ID` is stored | Default value: `PORT_CLIENT_ID` | | `PORT_CLIENT_SECRET_KEY` | key in the secret where the base64 encoded `PORT_CLIENT_SECRET` is stored | Default value: `PORT_CLIENT_SECRET` | | `BLUEPRINT_IDENTIFIER` | Identifier of the blueprint to create an entity of | **Required** | | `ENTITY_IDENTIFIER` | Identifier of the new (or existing) entity | Leave empty to get an auto-generated identifier | | `ENTITY_TITLE` | Title of the new (or existing) entity | | | `ENTITY_TEAM` | Teams array of the new (or existing) entity | | | `ENTITY_ICON` | Icon of the new (or existing) entity | | | `ENTITY_PROPERTIES` | Properties of the new (or existing) entity | | | `ENTITY_RELATIONS` | Relations of the new (or existing) entity. | | **Outputs** | Output | Description | Notes | | ------------------- | ------------------------------------------ | ----- | | `ENTITY_IDENTIFIER` | identifier of the new (or existing) entity | | ``` - name: entity-get templateRef: name: port template: entity-get arguments: parameters: # Note: if you save the CLIENT_ID and CLIENT_SECRET in the same format shown # in the portCredentials.yml file, there is no need to provide # PORT_CREDENTIALS_SECRET, PORT_CLIENT_ID_KEY, PORT_CLIENT_SECRET_KEY - name: PORT_CREDENTIALS_SECRET value: "port-credentials" - name: PORT_CLIENT_ID_KEY value: "PORT_CLIENT_ID" - name: PORT_CLIENT_SECRET_KEY value: "PORT_CLIENT_SECRET" - name: BLUEPRINT_IDENTIFIER value: "microservice" - name: ENTITY_IDENTIFIER value: "morp" ``` **Inputs** | Input | Description | Notes | | ------------------------- | ------------------------------------------------------------------------- | ----------------------------------- | | `PORT_CREDENTIALS_SECRET` | Name of the secret to get the `CLIENT_ID` and `CLIENT_SECRET` from | Default value: `port-credentials` | | `PORT_CLIENT_ID_KEY` | Key in the secret where the base64 encoded `PORT_CLIENT_ID` is stored | Default value: `PORT_CLIENT_ID` | | `PORT_CLIENT_SECRET_KEY` | Key in the secret where the base64 encoded `PORT_CLIENT_SECRET` is stored | Default value: `PORT_CLIENT_SECRET` | | `BLUEPRINT_IDENTIFIER` | Identifier of the blueprint the target entity is from | **Required** | | `ENTITY_IDENTIFIER` | Identifier of the target entity | | **Outputs** | Output | Description | Notes | | --------------------------- | ----------------------------------------------------- | ----- | | `PORT_COMPLETE_ENTITY` | Complete entity JSON | | | `PORT_BLUEPRINT_IDENTIFIER` | Identifier of the blueprint the target entity is from | | | `PORT_ENTITY_IDENTIFIER` | Identifier of the target entity | | | `PORT_ENTITY_TITLE` | Title of the entity | | | `PORT_ENTITY_PROPERTIES` | All properties of the entity in JSON format | | | `PORT_ENTITY_RELATIONS` | All relations of the entity in JSON format | | ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/codefresh-workflow-template/examples/.md) page for practical examples of Port's Codefresh workflow template. --- # Examples ## Basic create/update example[​](#basic-createupdate-example "Direct link to Basic create/update example") In this example you will create blueprints for `ciJob` and `image` entities, and a relation between them. Then you will add code that uses Port's Codefresh workflow template to create new entities every time the Codefresh CI workflow runs: **Image blueprint (click to expand)** Create in Port ``` { "identifier": "image", "title": "Image", "icon": "Docker", "schema": { "properties": { "synkHighVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk High Vaulnerabilities" }, "synkMediumVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk Medium Vaulnerabilities" }, "imageTag": { "type": "string", "title": "Image Tag" }, "gitRepoUrl": { "type": "string", "format": "url", "title": "Git Url", "description": "Git Url for the sourcecode" }, "imageRegistry": { "type": "string", "format": "url", "title": "Image Registry", "description": "Docker registry" }, "unitTestCoverage": { "type": "number", "title": "Unit Test coverage (%)", "description": "The unit test coverage pecentage" }, "integTestCoverage": { "type": "number", "title": "Integ Test coverage (%)", "description": "The integration test coverage pecentage" }, "size": { "type": "number", "title": "Image Size (GB)", "description": "The image size in Gigabyte" } }, "required": [] }, "calculationProperties": {} } ``` **CI Job blueprint (including the image relation) (click to expand)** Create in Port ``` { "identifier": "ciJob", "title": "CI Job", "icon": "CICD", "schema": { "properties": { "triggeredBy": { "type": "string", "icon": "TwoUsers", "title": "Triggered By", "description": "The user who triggered the run" }, "commitHash": { "type": "string", "title": "Commit Hash" }, "jobBranch": { "type": "string", "icon": "Git", "format": "url", "title": "Job branch" }, "runLink": { "type": "string", "format": "url", "title": "Action Run Link" } }, "required": [] }, "relations": { "image": { "title": "Job Images", "target": "image", "required": false, "many": true } }, "calculationProperties": {} } ``` After creating the blueprint, you can add the following snippet to your Codefresh workflow template `yml` file to create the new ciJob entity in your Codefresh CI: ``` - name: entity-upsert templateRef: name: port template: entity-upsert arguments: parameters: - name: PORT_CREDENTIALS_SECRET value: "port-credentials" - name: PORT_CLIENT_ID_KEY value: "PORT_CLIENT_ID" - name: PORT_CLIENT_SECRET_KEY value: "PORT_CLIENT_SECRET" - name: BLUEPRINT_IDENTIFIER value: "ciJob" - name: ENTITY_IDENTIFIER value: "new-cijob-run" - name: ENTITY_PROPERTIES value: | { "runLink": "${{CF_BUILD_URL}}", "triggeredBy": "${{CF_BUILD_INITIATOR}}" } ``` ## Basic get example[​](#basic-get-example "Direct link to Basic get example") The following example gets the `ciJob` entity from the previous example. This can be useful if your CI process needs to reference data from the ciJob (for example, the user who triggered the last job) when deploying the latest version of your service. Add the following steps to your workflow `yml` file: ``` - name: entity-get templateRef: name: port template: entity-get arguments: parameters: - name: PORT_CREDENTIALS_SECRET value: "port-credentials" - name: PORT_CLIENT_ID_KEY value: "PORT_CLIENT_ID" - name: PORT_CLIENT_SECRET_KEY value: "PORT_CLIENT_SECRET" - name: BLUEPRINT_IDENTIFIER value: "ciJob" - name: ENTITY_IDENTIFIER value: "new-cijob-run" ``` Using entity-get output For an example showing how to make use of the output from the `entity-get` workflow template, go to [complete example](#complete-example). ## Complete example[​](#complete-example "Direct link to Complete example") Here is a complete workflow `yml` to create both `image` entity, and a `cijob` entity with a relation between them, and then print the complete JSON of the new `image` entity: ``` apiVersion: argoproj.io/v1alpha1 kind: WorkflowTemplate metadata: name: port.codefresh-complete-example.0.0.1 annotations: port/version: "0.0.1" port/description: "This Workflow Template is used to create an image entity, a ciJob entity and then print the JSON of the new image" spec: arguments: parameters: entrypoint: ci-tasks templates: - name: ci-tasks metadata: annotations: dag: tasks: - name: entity-upsert-image templateRef: name: port template: entity-upsert arguments: parameters: - name: PORT_CREDENTIALS_SECRET value: "port-credentials" - name: PORT_CLIENT_ID_KEY value: "PORT_CLIENT_ID" - name: PORT_CLIENT_SECRET_KEY value: "PORT_CLIENT_SECRET" - name: BLUEPRINT_IDENTIFIER value: "image" - name: ENTITY_IDENTIFIER value: "example-image" - name: ENTITY_PROPERTIES value: | { "imageTag": "v1", "synkHighVulnerabilities": "0", "synkMediumVulnerabilities": "0", "gitRepoUrl": "https://github.com/my-org/my-cool-repo", "imageRegistry": "docker.io/cool-image", "size": "0.71", "unitTestCoverage": "20", "unitTestCoverage": "50", } - name: entity-upsert-cijob dependencies: [entity-upsert-image] templateRef: name: port template: entity-upsert arguments: parameters: - name: PORT_CREDENTIALS_SECRET value: "port-credentials" - name: PORT_CLIENT_ID_KEY value: "PORT_CLIENT_ID" - name: PORT_CLIENT_SECRET_KEY value: "PORT_CLIENT_SECRET" - name: BLUEPRINT_IDENTIFIER value: "ciJob" - name: ENTITY_IDENTIFIER value: "new-cijob-run" - name: ENTITY_PROPERTIES value: | { "runLink": "${{CF_BUILD_URL}}", "triggeredBy": "${{CF_BUILD_INITIATOR}}" } - name: ENTITY_RELATIONS value: | { "image": ["example-image"] } - name: entity-get dependencies: [entity-upsert-image, entity-upsert-cijob] templateRef: name: port template: entity-get arguments: parameters: - name: PORT_CREDENTIALS_SECRET value: port-credentials - name: PORT_CLIENT_ID_KEY value: PORT_CLIENT_ID - name: PORT_CLIENT_SECRET_KEY value: PORT_CLIENT_SECRET - name: BLUEPRINT_IDENTIFIER value: "image" - name: ENTITY_IDENTIFIER value: "example-image" - name: log-info dependencies: [entity-get] template: log-info arguments: parameters: - name: INFO value: "{{workflow.outputs.parameters.PORT_COMPLETE_ENTITY}}" - name: log-info inputs: parameters: - name: INFO script: image: alpine:latest command: ["/bin/sh"] env: - name: INFO value: "{{ inputs.parameters.INFO }}" source: | set -e echo ${INFO} ``` That's it! The Entity is created or updated and is visible in the UI. --- # GitHub Workflow Port's [GitHub action](https://github.com/marketplace/actions/port-github-action) allows you to create/update and query entities in Port directly from your GitHub workflows. ![GitHub Actions workflow using the Port action to manage software catalog entities](/img/build-your-software-catalog/sync-data-to-catalog/github/github-action-illustration.png) public repository The GitHub action is open source - see the repository [**here**](https://github.com/port-labs/port-github-action). ## 💡 Common Github workflow usage[​](#-common-github-workflow-usage "Direct link to 💡 Common Github workflow usage") Port's GitHub action provides a native way to integrate Port with your GitHub workflows, for example: * Report the status of a running **CI job**. * Update the software catalog about a new **build version** for a **microservice**. * Get existing **entities**. ## Usage[​](#usage "Direct link to Usage") To use Port's GitHub action, add the following line as a step in your GitHub workflow: ``` - uses: port-labs/port-github-action@v1 ```
Add your Port `CLIENT_ID` and `CLIENT_SECRET` as [GitHub secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets) in the relevant repository. ## Configuration[​](#configuration "Direct link to Configuration") Port's GitHub action supports the following operations: * Create/Update catalog entities - invoked with the `UPSERT` operation, receives the identifier and other properties of a new entity or an entity that needs to be updated. * Bulk Create/Update catalog entities - invoked with the `BULK_UPSERT` operation, receives the entities definitions of some new entities or entities that needs to be updated. * Get catalog entities - invoked with the `GET` operation, receives the identifier of an existing entity and retrieves it for use in your CI. * Search catalog entities - invoked with the `SEARCH` operation, receives a query and retrieves the entities for use in your CI. * Delete catalog entities - invoked with the `DELETE` operation, receives the identifier of an existing entity and deletes it. * Update a running action - invoked with the `PATCH_RUN` operation, receives the identifier of an existing action run along with other properties of the run that need to be updated. * Create an action run - invoked with the `CREATE_RUN` operation, receives the identifier of an existing blueprint, action and entity (optional), along with input properties to run the action with. - Create/Update Entity - Bulk Create/Update Entities - Get Entity - Search Entities - Delete Entity - Update Running Action - Create Action Run ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: UPSERT identifier: myEntity icon: myIcon blueprint: myBlueprint team: "['myTeam']" properties: |- { "myStringProp": "My value", "myNumberProp": 1, "myBooleanProp": true, "myArrayProp": ["myVal1", "myVal2"], "myObjectProp": {"myKey": "myVal", "myExtraKey": "myExtraVal"} } ``` ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: BULK_UPSERT runId: myRunId entities: |- [ { "identifier": "myEntity", "icon": "myIcon", "blueprint": "myBlueprint", "team": [ "myTeam" ], "properties": { "myStringProp": "My value", "myNumberProp": 1, "myBooleanProp": true, "myArrayProp": [ "myVal1", "myVal2" ], "myObjectProp": { "myKey": "myVal", "myExtraKey": "myExtraVal" } } } ] ``` ``` get-entity: runs-on: ubuntu-latest outputs: entity: ${{ steps.port-github-action.outputs.entity }} steps: - id: port-github-action uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: GET identifier: myEntity blueprint: myBlueprint use-entity: runs-on: ubuntu-latest needs: get-entity steps: - run: echo '${{needs.get-entity.outputs.entity}}' | jq .properties.myProp ``` ``` search-entities: runs-on: ubuntu-latest outputs: entities: ${{ steps.port-github-action.outputs.entities }} steps: - id: port-github-action uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: SEARCH query: |- { "rules": [ { "operator": "=", "value": "myEntity", "property": "$identifier" } ], "combinator": "and" } use-entities: runs-on: ubuntu-latest needs: search-entities steps: - run: echo '${{needs.search-entities.outputs.entities}}' | jq .[0].myProp ``` ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: DELETE identifier: myEntity blueprint: myBlueprint ``` ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: PATCH_RUN runId: myRunId status: "SUCCESS" logMessage: "My log message" summary: "My summary" link: '["https://mylink.com"]' ``` ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: CREATE_RUN icon: GithubActions blueprint: myBlueprint action: myAction identifier: myEntity properties: |- { "text": "test", "number": 1, "boolean": true } ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/github-workflow/examples/.md) page for practical examples of Port's GitHub action. --- # Examples ## Import Gradle dependencies[​](#import-gradle-dependencies "Direct link to Import Gradle dependencies") [This GitHub repository](https://github.com/matarpeles/extract-gradle-plugins-example) contains an implementation of a workflow that uses Port's GitHub action and a supporting Python script to import [Gradle](https://gradle.org/) dependencies into Port. The workflow achieves the following: * Extract plugins and their versions from a Gradle build file. * Map the extracted plugins as package entities in Port. * Automatically create or update a service entity in Port, connecting it to its plugin dependencies. See the [repository](https://github.com/matarpeles/extract-gradle-plugins-example) for the full implementation and a detailed README describing how to use it. ## Basic create/update example[​](#basic-createupdate-example "Direct link to Basic create/update example") In this example you will create blueprints for `ciJob` and `image` entities, and a relation between them. Using Port's GitHub action you will create new entities every time the GitHub workflow runs: **Image blueprint (click to expand)** Create in Port ``` { "identifier": "image", "title": "Image", "icon": "Docker", "schema": { "properties": { "synkHighVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk High Vaulnerabilities" }, "synkMediumVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk Medium Vaulnerabilities" }, "imageTag": { "type": "string", "title": "Image Tag" }, "gitRepoUrl": { "type": "string", "format": "url", "title": "Git Url", "description": "Git Url for the sourcecode" }, "imageRegistry": { "type": "string", "format": "url", "title": "Image Registry", "description": "Docker registry" }, "unitTestCoverage": { "type": "number", "title": "Unit Test coverage (%)", "description": "The unit test coverage pecentage" }, "integTestCoverage": { "type": "number", "title": "Integ Test coverage (%)", "description": "The integration test coverage pecentage" }, "size": { "type": "number", "title": "Image Size (GB)", "description": "The image size in Gigabyte" } }, "required": [] }, "calculationProperties": {} } ``` **CI Job blueprint (including the image relation) (click to expand)** Create in Port ``` { "identifier": "ciJob", "title": "CI Job", "icon": "CICD", "schema": { "properties": { "triggeredBy": { "type": "string", "icon": "TwoUsers", "title": "Triggered By", "description": "The user who triggered the run" }, "commitHash": { "type": "string", "title": "Commit Hash" }, "jobBranch": { "type": "string", "icon": "Git", "format": "url", "title": "Job branch" }, "runLink": { "type": "string", "format": "url", "title": "Action Run Link" } }, "required": [] }, "relations": { "image": { "title": "Job Images", "target": "image", "required": false, "many": true } }, "calculationProperties": {} } ``` After creating the blueprints, you can add the following snippet to your GitHub workflow `yml` file to create the `ciJob` entity in your GitHub workflow: ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: UPSERT identifier: new-cijob-run icon: GithubActions blueprint: ciJob properties: | { "commitHash": "${{ env.GITHUB_SHA }}", "jobBranch": "${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/tree/${{ env.GITHUB_REF_NAME }}", "runLink": "${{ env.GITHUB_SERVER_URL }}/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}", "triggeredBy": "${{ env.GITHUB_ACTOR }}" } ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Store credentials as GitHub Secrets For security reasons it is recommended to save the `CLIENT_ID` and `CLIENT_SECRET` as [GitHub Secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets), and access them as shown in the example above. ## Basic get example[​](#basic-get-example "Direct link to Basic get example") The following example gets the `ciJob` entity from the previous example. This can be useful if your CI process needs to reference data from the ciJob (for example, the user who triggered the last job) when deploying the latest version of your service. Add the following jobs to your GitHub workflow `yml` file: ``` get-entity: runs-on: ubuntu-latest outputs: entity: ${{ steps.port-github-action.outputs.entity }} steps: - id: port-github-action uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: GET identifier: new-cijob-run blueprint: ciJob use-entity: runs-on: ubuntu-latest needs: get-entity steps: - run: echo '${{needs.get-entity.outputs.entity}}' | jq .properties.triggeredBy ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` The first job `get-entity`, uses the GitHub action to get the `new-cijob-run` entity. The second job `use-entity`, uses the output from the first job, and prints the `triggeredBy` property of the entity. ## Relation example[​](#relation-example "Direct link to Relation example") The following example adds an `image` entity, in addition to the `ciJob` entity shown in the previous example. The `image` entity represents an artifact generated by the ciJob run. Add the following snippet to your GitHub workflow `yml` file: ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: UPSERT identifier: example-image title: Example Image icon: Docker blueprint: image properties: | { "imageTag": "v1", "synkHighVulnerabilities": "0", "synkMediumVulnerabilities": "0", "gitRepoUrl": "https://github.com/my-org/my-cool-repo", "imageRegistry": "docker.io/cool-image", "size": "0.71", "unitTestCoverage": "20", "unitTestCoverage": "50", } ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` All that’s left is to map the new `image` entity to the `ciJob` , thus making it possible to know which image was created by the ciJob. ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: UPSERT identifier: new-cijob-run icon: GithubActions blueprint: ciJob relations: | { "image": ["example-image"] } ``` That's it! The entities are updated and visible in the UI. ## Basic update run example[​](#basic-update-run-example "Direct link to Basic update run example") In this example you will create a self-service action that deploys the latest version of your service, and updates the action run with the deployment status. Add the following action to your `image` blueprint actions: **Deploy image action (click to expand)** Create in Port ``` { "identifier": "deploy_image", "title": "Deploy image", "icon": "DefaultProperty", "description": "Deploy image version", "trigger": { "type": "self-service", "operation": "DAY-2", "userInputs": { "properties": {}, "required": [], "order": [] }, "blueprintIdentifier": "image" }, "invocationMethod": { "type": "WEBHOOK", "url": "https://example.com", "agent": false, "synchronized": false, "method": "POST", "headers": { "RUN_ID": "{{ .run.id }}" }, "body": { "action": "{{ .action }}", "trigger": "{{ .trigger | {by, origin, at} }}", "port_context": { "entity": "{{.entity.identifier}}", "blueprint": "{{.action.blueprint}}", "runId": "{{.run.id}}", "operation": "{{.trigger.operation}}" } } }, "requiredApproval": false } ``` GitHub workflow required The example here is meant to show a common flow when using a Port self-service action and then using Port's GitHub action to update its logs, status and other information. In order to use Port's GitHub action to make these updates, you will need your backend to either be a GitHub workflow or for a different backend of your choosing to trigger a GitHub workflow as part of its logic After triggering the action in Port, a new action run will be created in Port (and a matching `runId` will be generated). The runId can be used to update the action status and reports logs to Port. To update the new self-service action run, add the following snippet to your GitHub workflow `yml` file (note you will need to pass the correct `runId` to the action): ``` - uses: port-labs/port-github-action@v1 with: clientId: ${{ secrets.CLIENT_ID }} clientSecret: ${{ secrets.CLIENT_SECRET }} baseUrl: https://api.port.io operation: PATCH_RUN runId: ${{ env.PORT_RUN_ID }} icon: GithubActions status: "SUCCESS" logMessage: "Deployment completed successfully" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Update status last The example above shows how to update the status and add a new log message to the action run, but it is also possible to update just a specific field of an action run. For example it is possible to trigger the GitHub action and just update the log, without changing its status. Note that once a Port action run has a status, it can no longer be updated and changes made to the catalog can no longer be tied to that action, so it is considered a best practice to update the status of an action only when it has finished performing all of its catalog changes and logic That's it! The action status and logs are updated in Port. --- # Gitlab CI Pipelines Using Gitlab CI Pipelines, you can easily create/update and query entities in Port.

![GitLab CI/CD job sending catalog data to Port for display in the web app](/assets/images/gitlab-pipelines-illustration-4b516fdb48ad47b4da483e8cb0a8f71d.png) ## 💡 Common Gitlab CI Pipelines usage[​](#-common-gitlab-ci-pipelines-usage "Direct link to 💡 Common Gitlab CI Pipelines usage") Port's API allows for easy integration between Port and your Gitlab CI Pipeline jobs, for example: * Report the status of a running **CI job**. * Update the software catalog about a new **build version** for a **microservice**. * Get existing **entities**. ## Setup[​](#setup "Direct link to Setup") To interact with Port using Gitlab CI Pipeline, you will first need to [define your Port credentials](https://docs.gitlab.com/ee/ci/variables/index.html#define-a-cicd-variable-in-the-ui) as variables for your pipeline. Then, pass the defined variables to your ci pipeline script, for example, `Python`: ``` image: python:3.9 variables: PORT_CLIENT_ID: $PORT_CLIENT_ID # The variable name for your Port clientId PORT_CLIENT_SECRET: $PORT_CLIENT_SECRET # The variable name for your Port clientSecret report_to_port: stage: build script: - python main.py ``` Make sure you have an existing [Blueprint](/context-lake/data-model/setup-blueprint/overview/.md) in your Port installation to create/update entities. ## Working with Port's API[​](#working-with-ports-api "Direct link to Working with Port's API") Here is an example snippet showing how to integrate a job that uses Port's API with your existing Gitlab CI pipelines using Python: Add the following task to your Gitlab pipeline: **Gitlab pipeline YAML (click to expand)** ``` image: python:3.9 variables: PORT_CLIENT_ID: $PORT_CLIENT_ID PORT_CLIENT_SECRET: $PORT_CLIENT_SECRET stages: - build report_to_port: stage: build before_script: - python -m pip install --upgrade pip - pip install -r requirements.txt script: - python main.py ```
Python dependencies required In the following example, we use Python modules which need to be installed. You can use the following `requirements.txt`: **port\_requirements.txt (click to expand)** ``` requests>=2.28.2 ``` * Create/Update * Get Create the following Python script in your repository to create or update Port entities as part of your pipeline: ``` import os import requests import json # These are the credentials passed by the variables of your pipeline to your tasks and in to your env CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] API_URL = 'https://api.port.io/v1' credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) # use this access token + header for all http requests to Port access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } entity_json = { "identifier": "example-entity", "properties": { "myStringProp": "My value", "myNumberProp": 1, "myBooleanProp": true, "myArrayProp": ["myVal1", "myVal2"], "myObjectProp": {"myKey": "myVal", "myExtraKey": "myExtraVal"} } } # request url : {API_URL}/blueprints//entities create_response = requests.post(f'{API_URL}/blueprints/test-blueprint/entities?upsert=true', json=entity_json, headers=headers) print(json.dumps(create_response.json(), indent=4)) ``` Create the following Python script in your repository to get Port entities as part of your pipeline: ``` import os import requests import json # These are the credentials passed by the variables of your pipeline to your tasks and in to your env CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] API_URL = 'https://api.port.io/v1' credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f'{API_URL}/auth/access_token', json=credentials) # use this access token + header for all http requests to Port access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } # request url : {API_URL}/blueprints//entities/ get_response = requests.get(f"{API_URL}/blueprints/test-blueprint/entities/test-entity", headers=headers) print(json.dumps(get_response.json(), indent=4)) ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/gitlab-pipelines/examples/.md) page for practical examples of working with Port using Gitlab CI Pipelines. --- # Examples ## Basic create/update example[​](#basic-createupdate-example "Direct link to Basic create/update example") In this example you will create blueprints for `ciJob` and `image` entities, and a relation between them. Then you will add some Python code to create new entities in Port every time the Gitlab Pipeline is triggered: **Image blueprint (click to expand)** Create in Port ``` { "identifier": "image", "title": "Image", "icon": "Docker", "schema": { "properties": { "synkHighVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk High Vaulnerabilities" }, "synkMediumVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk Medium Vaulnerabilities" }, "imageTag": { "type": "string", "title": "Image Tag" }, "gitRepoUrl": { "type": "string", "format": "url", "title": "Git Url", "description": "Git Url for the sourcecode" }, "imageRegistry": { "type": "string", "format": "url", "title": "Image Registry", "description": "Docker registry" }, "unitTestCoverage": { "type": "number", "title": "Unit Test coverage (%)", "description": "The unit test coverage pecentage" }, "integTestCoverage": { "type": "number", "title": "Integ Test coverage (%)", "description": "The integration test coverage pecentage" }, "size": { "type": "number", "title": "Image Size (GB)", "description": "The image size in Gigabyte" } }, "required": [] }, "calculationProperties": {} } ``` **CI Job blueprint (including the image relation) (click to expand)** Create in Port ``` { "identifier": "ciJob", "title": "CI Job", "icon": "CICD", "schema": { "properties": { "triggeredBy": { "type": "string", "icon": "TwoUsers", "title": "Triggered By", "description": "The user who triggered the run" }, "commitHash": { "type": "string", "title": "Commit Hash" }, "jobBranch": { "type": "string", "icon": "Git", "format": "url", "title": "Job branch" }, "runLink": { "type": "string", "format": "url", "title": "Action Run Link" } }, "required": [] }, "relations": { "image": { "title": "Job Images", "target": "image", "required": false, "many": true } }, "calculationProperties": {} } ``` After creating the blueprint, you can add a Python script for creating/updating a Port entity: ``` import os import requests import json # Env vars passed by the pipeline variables CLIENT_ID = os.environ['PORT_CLIENT_ID'] CLIENT_SECRET = os.environ['PORT_CLIENT_SECRET'] API_URL = 'https://api.port.io/v1' credentials = { 'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET } token_response = requests.post(f"{API_URL}/auth/access_token", json=credentials) # use this access token + header for all http requests to Port access_token = token_response.json()['accessToken'] headers = { 'Authorization': f'Bearer {access_token}' } entity_json = { "identifier": "new-cijob-run", "properties": { "triggeredBy": os.environ['QUEUED_BY'], "commitHash": os.environ['GIT_SHA'], "actionJob": os.environ['JOB_NAME'], "jobLink": os.environ['JOB_URL'] }, "relations": { "image": ["example-image"] } } create_response = requests.post(f'{API_URL}/blueprints/{blueprint_id}/entities?upsert=true&create_missing_related_entities=true', json=entity_json, headers=headers) ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Relation auto-created Please notice that you have also created the `image` relation, and added a related image entity called `example-image`. This is the artifact of the ciJob, and you will update it later. The creation was done using the `create_missing_related_entities=true` flag in the API url, allowing the relation to be created even though the `example-image` entity doesn't exist yet. After adding your new Python script to your repository, add the following code to your Gitlab pipeline `yml` file to call your script and update/create a new `ciJob` entity: ``` image: python:3.9 variables: PORT_CLIENT_ID: $PORT_CLIENT_ID PORT_CLIENT_SECRET: $PORT_CLIENT_SECRET QUEUED_BY: $CI_COMMIT_AUTHOR GIT_SHA: $CI_COMMIT_SHA JOB_NAME: $CI_JOB_NAME RUN_LINK: $CI_PIPELINE_URL stages: - build report_to_port: stage: build before_script: - python -m pip install --upgrade pip - pip install -r requirements.txt script: - python main.py ``` ## Basic get example[​](#basic-get-example "Direct link to Basic get example") The following example gets the `new-cijob-run` entity from the previous example, this can be useful if your CI process creates a build artifact and then references some of its data (for example, the run link of the latest `ciJob`). Add the following snippet to your Python code: ``` entity_id = "new-cijob-run" blueprint_id = "ciJob" get_response = requests.get(f"{API_URL}/blueprints/{blueprint_id}/entities/{entity_id}", headers=headers) entity = get_response.json()['entity'] print(f"Image tag is: {entity['properties']['runLink']}") ``` ## Relation example[​](#relation-example "Direct link to Relation example") The following example demonstrates how to update the `example-image` entity that was automatically created when creating the `ciJob` entity shown in the previous example. Add the following snippet to your Python code: ``` image_entity_json = { "identifier": "example-image", "team": [], "properties": { "imageTag": "v1", "synkHighVulnerabilities": "0", "synkMediumVulnerabilities": "0", "gitRepoUrl": "https://github.com/my-org/my-cool-repo", "imageRegistry": "docker.io/cool-image", "size": "0.71", "unitTestCoverage": "20", "unitTestCoverage": "50" }, "relations": {} } create_image_response = requests.post(f'{API_URL}/blueprints/image/entities?upsert=true', json=image_entity_json, headers=headers) ``` That's it! The entity is created or updated and is visible in the UI. --- # Jenkins Deployment Available Ocean integration Port provides an [Ocean integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/jenkins/.md) for Jenkins, which allows you to automatically sync your Jenkins resources with Port and provides more configuration options. This is the **recommended** way to integrate Port with Jenkins.
If you would still prefer to use Port's API, follow this page. Using Jenkins build, you can easily create/update and query entities in Port.

![Jenkins pipeline reporting data to the Port software catalog for display in the web app](/assets/images/jenkins-pipeline-illustration-3a299aa823a294cbe653f3a0091b8738.png) ## 💡 Common Jenkins build usage[​](#-common-jenkins-build-usage "Direct link to 💡 Common Jenkins build usage") Port's API allows for easy integration between Port and your Jenkins builds, for example: * Report the status of a running **CI job**. * Update the software catalog about a new **build version** for a **microservice**. * Get existing **entities**. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") 1. This example makes use of the following Jenkins plugins: * [Plain Credentials](https://plugins.jenkins.io/credentials-binding/) (>=143.v1b\_df8b\_d3b\_e48) * [HTTP Request](https://plugins.jenkins.io/http_request/) (>=1.16) 2. The following methods are used in the example, and these signatures need to be approved: ``` new groovy.json.JsonSlurperClassic method groovy.json.JsonSlurperClassic parseText java.lang.String ``` 3. Add your `PORT_CLIENT_ID` and `PORT_CLIENT_SECRET` as [Jenkins Credentials](https://www.jenkins.io/doc/book/using/using-credentials/) to use them in your CI pipelines. 4. Make sure you have an existing [Blueprint](/context-lake/data-model/setup-blueprint/overview/.md) in your Port installation to create/update entities. ## Set up[​](#set-up "Direct link to Set up") API routes reference All Port API routes used in this guide can be found in Port's [API documentation](/api-reference/port-api/). To interact with Port inside your Jenkins builds, follow these steps: ### Fetching your API token[​](#fetching-your-api-token "Direct link to Fetching your API token") The following snippet shows you how to pass your `PORT_CLIENT_ID` and `PORT_CLIENT_SECRET` credentials to your build using `withCredentials`, which utilizes the Plain Credentials plugin to bind credentials to variables. The snippet provided also includes saving Port's API URL as an environment variable for use in future stages and makes a request to Port's API using the credentials to get an access token: **Get API token (click to expand)** ``` pipeline { agent any environment { API_URL = "https://api.port.io" } ... withCredentials([ string(credentialsId: 'port-client-id', variable: 'PORT_CLIENT_ID'), string(credentialsId: 'port-client-secret', variable: 'PORT_CLIENT_SECRET') ]){ // Token request body auth_body = """ { "clientId": "${PORT_CLIENT_ID}", "clientSecret": "${PORT_CLIENT_SECRET}" } """ // Make a request to fetch Port API's token token_response = httpRequest contentType: 'APPLICATION_JSON', httpMode: "POST", requestBody: auth_body, url: "${API_URL}/v1/auth/access_token" // Parse the response to get the accessToken def slurped_response = new JsonSlurperClassic().parseText(token_response.content) def token = slurped_response.accessToken // Use this token for authentication with Port ... } ``` ### Working with Port's API[​](#working-with-ports-api "Direct link to Working with Port's API") Add the following code to your Jenkins build, to either create/update an entity, or get an existing one: * Create/Update * Get ``` import groovy.json.JsonSlurperClassic ... auth_body = """ { "clientId": "${PORT_CLIENT_ID}", "clientSecret": "${PORT_CLIENT_SECRET}" } """ token_response = httpRequest contentType: 'APPLICATION_JSON', httpMode: "POST", requestBody: auth_body, url: "${API_URL}/v1/auth/access_token" def slurped_response = new JsonSlurperClassic().parseText(token_response.content) def token = slurped_response.accessToken // Port's access token entity_body = """ { "identifier": "example-entity", "properties": { "myStringProp": "My value", "myNumberProp": 1, "myBooleanProp": true, "myArrayProp": ["myVal1", "myVal2"], "myObjectProp": {"myKey": "myVal", "myExtraKey": "myExtraVal"} } } """ // request url : {API_URL}/blueprints//entities/ response = httpRequest contentType: "APPLICATION_JSON", httpMode: "POST", url: "${API_URL}/v1/blueprints/blueprint/entities?upsert=true&merge=true", requestBody: entity_body, customHeaders: [ [name: "Authorization", value: "Bearer ${token}"], ] println(response.content) ``` ``` import groovy.json.JsonSlurperClassic ... auth_body = """ { "clientId": "${PORT_CLIENT_ID}", "clientSecret": "${PORT_CLIENT_SECRET}" } """ token_response = httpRequest contentType: 'APPLICATION_JSON', httpMode: "POST", requestBody: auth_body, url: "${API_URL}/v1/auth/access_token" def slurped_response = new JsonSlurperClassic().parseText(token_response.content) def token = slurped_response.accessToken // Port's access token response = httpRequest contentType: 'APPLICATION_JSON', httpMode: "GET", url: "${API_URL}/v1/blueprints/blueprint/entities/entity-example", customHeaders: [ [name: "Authorization", value: "Bearer ${token}"], ] println(response.content) ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/jenkins-deployment/examples/.md) page for practical examples for using Port with Jenkins. --- # Examples ## Basic create/update example[​](#basic-createupdate-example "Direct link to Basic create/update example") Build user vars plugin required For this example, the following plugin is a dependency: * build user vars plugin (>=v1.9) In this example you will create blueprints for `ciJob` and `image` entities, and a relation between them. Then you will add some Groovy code to create new entities in Port every time the Jenkins pipeline is triggered: **Image blueprint (click to expand)** Create in Port ``` { "identifier": "image", "title": "Image", "icon": "Docker", "schema": { "properties": { "synkHighVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk High Vaulnerabilities" }, "synkMediumVulnerabilities": { "type": "string", "icon": "Snyk", "title": "Synk Medium Vaulnerabilities" }, "imageTag": { "type": "string", "title": "Image Tag" }, "gitRepoUrl": { "type": "string", "format": "url", "title": "Git Url", "description": "Git Url for the sourcecode" }, "imageRegistry": { "type": "string", "format": "url", "title": "Image Registry", "description": "Docker registry" }, "unitTestCoverage": { "type": "number", "title": "Unit Test coverage (%)", "description": "The unit test coverage pecentage" }, "integTestCoverage": { "type": "number", "title": "Integ Test coverage (%)", "description": "The integration test coverage pecentage" }, "size": { "type": "number", "title": "Image Size (GB)", "description": "The image size in Gigabyte" } }, "required": [] }, "calculationProperties": {} } ``` **CI Job blueprint (including the image relation) (click to expand)** Create in Port ``` { "identifier": "ciJob", "title": "CI Job", "icon": "CICD", "schema": { "properties": { "triggeredBy": { "type": "string", "icon": "TwoUsers", "title": "Triggered By", "description": "The user who triggered the run" }, "commitHash": { "type": "string", "title": "Commit Hash" }, "jobBranch": { "type": "string", "icon": "Git", "format": "url", "title": "Job branch" }, "runLink": { "type": "string", "format": "url", "title": "Action Run Link" } }, "required": [] }, "relations": { "image": { "title": "Job Images", "target": "image", "required": false, "many": true } }, "calculationProperties": {} } ``` Token and API\_URL from setup The variables `token` and `API_URL` are used in the following examples. Click [here](/context-lake/ingestion/ingest-data-into-port/api/ci-cd/jenkins-deployment/.md#fetching-your-api-token) to see how they were defined. After creating the blueprint, you can add the following snippet to your Jenkins build to create the new build entity: ``` // Use the `build users` plugin to fetch the triggering user's ID wrap([$class: 'BuildUser']) { def user = env.BUILD_USER_ID } def body = """ { "identifier": "new-cijob-run", "properties": { "triggeredBy": "${user}", "runLink": "${env.BUILD_URL}", }, "relations": { "image": ["example-image"] } } """ response = httpRequest contentType: "APPLICATION_JSON", httpMode: "POST", url: "${API_URL}/v1/blueprints/ciJob/entities?upsert=true&merge=true&create_missing_related_entities=true", requestBody: body, customHeaders: [ [name: "Authorization", value: "Bearer ${token}"], ] ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Relation auto-created Please notice that you have also created the `image` relation, and added a related image entity called `example-image`. This is the artifact of the ciJob, and you will update it later. The creation was done using the `create_missing_related_entities=true` flag in the API url, allowing the relation to be created even though the `example-image` entity doesn't exist yet. ## Basic get example[​](#basic-get-example "Direct link to Basic get example") The following example gets the `new-cijob-run` entity from the previous example, this can be useful if your CI process creates a build artifact and then references some of its data (for example, the run link of the latest `ciJob`). Add the following code to your Jenkins build: ``` response = httpRequest contentType: "APPLICATION_JSON", httpMode: "GET", url: "${API_URL}/v1/blueprints/ciJob/entities/new-cijob-run", customHeaders: [ [name: "Authorization", value: "Bearer ${token}"], ] println(response.content) ``` ## Relation example[​](#relation-example "Direct link to Relation example") The following example demonstrates how to update the `example-image` entity that was automatically created when creating the `ciJob` entity shown in the previous example. Add the following snippet to your Jenkins Pipeline: ``` def body = """ { "identifier": "example-image", "properties": { "imageTag": "v1", "synkHighVulnerabilities": "0", "synkMediumVulnerabilities": "0", "gitRepoUrl": "https://github.com/my-org/my-cool-repo", "imageRegistry": "docker.io/cool-image", "size": "0.71", "unitTestCoverage": "20", "unitTestCoverage": "50" }, "relations": {} } """ response = httpRequest contentType: "APPLICATION_JSON", httpMode: "POST", url: "${API_URL}/v1/blueprints/image/entities?upsert=true&merge=true", requestBody: body, customHeaders: [ [name: "Authorization", value: "Bearer ${token}"], ] ``` That's it! The entity is created or updated and is visible in the UI. --- # Ingest via API Port's [API](/api-reference/port-api/) is a generic interface to model your software catalog, ingest data, invoke actions, query scorecards and more. ## 💡 Common Port API usage[​](#-common-port-api-usage "Direct link to 💡 Common Port API usage") Since Port API is a generic interface, anything that can be done with Port is possible through the API, for example: * Update the software catalog using a script. * Import your existing asset inventory from a CSV file. * Integrate Port with your custom CI/CD. * Report the status of a running **CI job**. * Update the software catalog about a new **build version** for a **microservice**. * Get existing data from your software catalog. ## Get API Token[​](#get-api-token "Direct link to Get API Token") In order to interact with the API you will need an API token. Getting an API token involves 2 steps: 1. Finding your Port API credentials. 2. Making an API request to generate a valid token. ### Find your Port credentials[​](#find-your-port-credentials "Direct link to Find your Port credentials") To get your Port credentials, open the [Credentials](https://app.port.io/organization/home?openModal=credentials) modal. Here you can view and copy your `CLIENT_ID` and `CLIENT_SECRET`: ![Port Credentials modal showing organization, client ID, client secret, and API token controls](/img/software-catalog/credentials-modal.png) ### Generate an API token[​](#generate-an-api-token "Direct link to Generate an API token") Here are some code examples showing how to generate an API token in various programming languages: * Python * Javascript * cURL ``` # Dependencies to install: # $ python -m pip install requests import requests CLIENT_ID = 'YOUR_CLIENT_ID' CLIENT_SECRET = 'YOUR_CLIENT_SECRET' API_URL = 'https://api.port.io/v1' credentials = {'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET} token_response = requests.post(f'{API_URL}/auth/access_token', json=credentials) access_token = token_response.json()['accessToken'] # You can now use the value in access_token when making further requests ``` ``` // Dependencies to install: // $ npm install axios --save const axios = require("axios").default; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const API_URL = "https://api.port.io/v1"; (async () => { const response = await axios.post(`${API_URL}/auth/access_token`, { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, }); const accessToken = response.data.accessToken; // You can now use the value in accessToken when making further requests })(); ``` ``` # Dependencies to install: # For apt: # $ sudo apt-get install jq # For yum: # $ sudo yum install jq access_token=$(curl --location --request POST 'https://api.port.io/v1/auth/access_token' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }' | jq '.accessToken' | sed 's/"//g') # The token will be available in the access_token variable ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Ingest data via API[​](#ingest-data-via-api "Direct link to Ingest data via API") Since Port is API-first it is possible to create and update entities using simple REST calls from any platform you use. ### Setup[​](#setup "Direct link to Setup") To use Port's REST API you need to perform the following steps: 1. Find your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials). 2. Save them as secrets or in some other same manner such that you can reference them in your code or CI/CD flow. 3. Make sure you have an HTTP-capable client. 1. For example: cURL, python with the requests package, nodejs with fetch/axios, etc. ### Usage[​](#usage "Direct link to Usage") Since you are using Port's REST API directly, any method that the API provides is at your disposal. We will focus on three specific use cases: * **Get** catalog entities - available by making HTTP GET requests to `https://api.port.io/v1/blueprints/{blueprint_identifier}/entities/{entity_identifier}`, receives the identifier of an existing entity and retrieves it for use in your CI. * **Create/Update** catalog entities - available by making HTTP POST requests to `https://api.port.io/v1/blueprints/{blueprint_identifier}/entities/`, receives the identifier and other properties of a new entity or an entity that needs to be updated. * **Delete** catalog entities - available by making HTTP DELETE requests to `https://api.port.io/v1/blueprints/{blueprint_identifier}/entities/{entity_identifier}`, receives the identifier of an existing entity and deletes it. - Get - Create - Create/Update - Create/Override - Delete - Delete All * Python * Javascript * cURL ``` # Dependencies to install: # $ python -m pip install requests import requests CLIENT_ID = 'YOUR_CLIENT_ID' CLIENT_SECRET = 'YOUR_CLIENT_SECRET' API_URL = 'https://api.port.io/v1' credentials = {'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET} token_response = requests.post(f'{API_URL}/auth/access_token', json=credentials) access_token = token_response.json()['accessToken'] # You can now use the value in access_token when making further requests headers = { 'Authorization': f'Bearer {access_token}' } blueprint_id = 'MY_BLUEPRINT' entity_id = 'MY_ENTITY_IDENTIFIER' response = requests.get(f'{API_URL}/blueprints/{blueprint_id}/entities/{entity_id}', headers=headers) # Entity is in response.json()["entity"] ``` ``` // Dependencies to install: // $ npm install axios --save const axios = require("axios").default; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const API_URL = "https://api.port.io/v1"; (async () => { const response = await axios.post(`${API_URL}/auth/access_token`, { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, }); const accessToken = response.data.accessToken; // You can now use the value in accessToken when making further requests const blueprintId = "MY_BLUEPRINT"; const entityId = "MY_ENTITY_IDENTIFIER"; const config = { headers: { Authorization: `Bearer ${accessToken}`, }, }; const getResponse = await axios.get( `${API_URL}/blueprints/${blueprintId}/entities/${entityId}`, config ); // Entity is in getResponse.data.entity })(); ``` ``` # Dependencies to install: # for brew: # brew install jq # For apt: # $ sudo apt-get install jq # For yum: # $ sudo yum install jq access_token=$(curl --location --request POST 'https://api.port.io/v1/auth/access_token' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }' | jq '.accessToken' | sed 's/"//g') # The token will be available in the access_token variable blueprint_id='MY_BLUEPRINT' entity_id='MY_ENTITY_IDENTIFIER' curl --location --request GET "https://api.port.io/v1/blueprints/${blueprint_id}/entities/${entity_id}" \ --header "Authorization: Bearer $access_token" \ --header "Content-Type: application/json" # Use jq '.entity' to read the entity ``` A basic create request will create a new entity if one with the provided `identifier` doesn't exist yet, and will fail with a `409 CONFLICT` status code otherwise. A basic create request is noted by providing no query parameters, or providing the `upsert` query parameter with its value set to `false`. * Python * Javascript * cURL ``` # Dependencies to install: # $ python -m pip install requests import requests CLIENT_ID = 'YOUR_CLIENT_ID' CLIENT_SECRET = 'YOUR_CLIENT_SECRET' API_URL = 'https://api.port.io/v1' credentials = {'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET} token_response = requests.post(f'{API_URL}/auth/access_token', json=credentials) access_token = token_response.json()['accessToken'] # You can now use the value in access_token when making further requests headers = { 'Authorization': f'Bearer {access_token}' } blueprint_id = 'MY_BLUEPRINT' entity = { 'identifier': 'MY_ENTITY_IDENTIFIER', 'title': 'MY TITLE', 'properties': { 'MY_STRING_PROP': 'MY VALUE', 'MY_BOOLEAN_PROP': False }, 'relations': {} } response = requests.post(f'{API_URL}/blueprints/{blueprint_id}/entities', json=entity, headers=headers) # Entity is in response.json()["entity"] ``` ``` // Dependencies to install: // $ npm install axios --save const axios = require("axios").default; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const API_URL = "https://api.port.io/v1"; (async () => { const response = await axios.post(`${API_URL}/auth/access_token`, { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, }); const accessToken = response.data.accessToken; // You can now use the value in accessToken when making further requests const blueprintId = "MY_BLUEPRINT"; const config = { headers: { Authorization: `Bearer ${accessToken}`, }, }; const entity = { identifier: "MY_ENTITY_IDENTIFIER", title: "MY TITLE", properties: { MY_STRING_PROP: "MY VALUE", MY_BOOLEAN_PROP: false, }, relations: {}, }; const postResponse = await axios.post( `${API_URL}/blueprints/${blueprintId}/entities`, entity, config ); // Entity is in postResponse.data.entity })(); ``` ``` # Dependencies to install: # For apt: # $ sudo apt-get install jq # For yum: # $ sudo yum install jq access_token=$(curl --location --request POST 'https://api.port.io/v1/auth/access_token' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }' | jq '.accessToken' | sed 's/"//g') # The token will be available in the access_token variable blueprint_id='MY_BLUEPRINT' curl --location --request POST "https://api.port.io/v1/blueprints/${blueprint_id}/entities" \ --header "Authorization: Bearer $access_token" \ --header "Content-Type: application/json" \ --data-raw '{ "identifier": "MY_ENTITY_IDENTIFIER", "title": "MY ENTITY TITLE", "properties": { "MY_STRING_PROP": "MY VALUE", "MY_BOOLEAN_PROP": false } }' # Use jq '.entity' to read the entity ``` A create/update request will create a new entity if one with the provided `identifier` doesn't exist yet, and will only update the values of fields provided in the request, if an entity with the `identifier` already exists. A create/update request is noted by providing the query parameter: `upsert=true&merge=true`. * Python * Javascript * cURL ``` # Dependencies to install: # $ python -m pip install requests import requests CLIENT_ID = 'YOUR_CLIENT_ID' CLIENT_SECRET = 'YOUR_CLIENT_SECRET' API_URL = 'https://api.port.io/v1' credentials = {'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET} token_response = requests.post(f'{API_URL}/auth/access_token', json=credentials) access_token = token_response.json()['accessToken'] # You can now use the value in access_token when making further requests headers = { 'Authorization': f'Bearer {access_token}' } blueprint_id = 'MY_BLUEPRINT' entity = { 'identifier': 'MY_ENTITY_IDENTIFIER', 'title': 'MY TITLE', 'properties': { 'MY_STRING_PROP': 'MY VALUE', 'MY_BOOLEAN_PROP': False }, 'relations': {} } # Note the ?upsert=true&merge=true query parameters response = requests.post(f'{API_URL}/blueprints/{blueprint_id}/entities?upsert=true&merge=true', json=entity, headers=headers) # Entity is in response.json()["entity"] ``` ``` // Dependencies to install: // $ npm install axios --save const axios = require("axios").default; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const API_URL = "https://api.port.io/v1"; (async () => { const response = await axios.post(`${API_URL}/auth/access_token`, { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, }); const accessToken = response.data.accessToken; // You can now use the value in accessToken when making further requests const blueprintId = "MY_BLUEPRINT"; const config = { headers: { Authorization: `Bearer ${accessToken}`, }, }; const entity = { identifier: "MY_ENTITY_IDENTIFIER", title: "MY TITLE", properties: { MY_STRING_PROP: "MY VALUE", MY_BOOLEAN_PROP: false, }, relations: {}, }; // Note the ?upsert=true&merge=true query parameters const postResponse = await axios.post( `${API_URL}/blueprints/${blueprintId}/entities?upsert=true&merge=true`, entity, config ); // Entity is in postResponse.data.entity })(); ``` ``` # Dependencies to install: # For apt: # $ sudo apt-get install jq # For yum: # $ sudo yum install jq access_token=$(curl --location --request POST 'https://api.port.io/v1/auth/access_token' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }' | jq '.accessToken' | sed 's/"//g') # The token will be available in the access_token variable blueprint_id='MY_BLUEPRINT' # Note the ?upsert=true&merge=true query parameters curl --location --request POST "https://api.port.io/v1/blueprints/${blueprint_id}/entities?upsert=true&merge=true" \ --header "Authorization: Bearer $access_token" \ --header "Content-Type: application/json" \ --data-raw '{ "identifier": "MY_ENTITY_IDENTIFIER", "title": "MY ENTITY TITLE", "properties": { "MY_STRING_PROP": "MY VALUE", "MY_BOOLEAN_PROP": false } }' # Use jq '.entity' to read the entity ``` A create/override request will create a new entity if one with the provided `identifier` doesn't exist yet, and will override all existing values if an entity with the `identifier` already exists, keeping only the values of fields provided in the request. A create/override request is noted by providing the query parameter: `upsert=true`, while not including the `merge` parameter (or setting its value to `false`) * Python * Javascript * cURL ``` # Dependencies to install: # $ python -m pip install requests import requests CLIENT_ID = 'YOUR_CLIENT_ID' CLIENT_SECRET = 'YOUR_CLIENT_SECRET' API_URL = 'https://api.port.io/v1' credentials = {'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET} token_response = requests.post(f'{API_URL}/auth/access_token', json=credentials) access_token = token_response.json()['accessToken'] # You can now use the value in access_token when making further requests headers = { 'Authorization': f'Bearer {access_token}' } blueprint_id = 'MY_BLUEPRINT' entity = { 'identifier': 'MY_ENTITY_IDENTIFIER', 'title': 'MY TITLE', 'properties': { 'MY_STRING_PROP': 'MY VALUE', 'MY_BOOLEAN_PROP': False }, 'relations': {} } # Note the ?upsert=true query parameter response = requests.post(f'{API_URL}/blueprints/{blueprint_id}/entities?upsert=true', json=entity, headers=headers) # Entity is in response.json()["entity"] ``` ``` // Dependencies to install: // $ npm install axios --save const axios = require("axios").default; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const API_URL = "https://api.port.io/v1"; (async () => { const response = await axios.post(`${API_URL}/auth/access_token`, { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, }); const accessToken = response.data.accessToken; // You can now use the value in accessToken when making further requests const blueprintId = "MY_BLUEPRINT"; const config = { headers: { Authorization: `Bearer ${accessToken}`, }, }; const entity = { identifier: "MY_ENTITY_IDENTIFIER", title: "MY TITLE", properties: { MY_STRING_PROP: "MY VALUE", MY_BOOLEAN_PROP: false, }, relations: {}, }; // Note the ?upsert=true query parameter const postResponse = await axios.post( `${API_URL}/blueprints/${blueprintId}/entities?upsert=true`, entity, config ); // Entity is in postResponse.data.entity })(); ``` ``` # Dependencies to install: # For apt: # $ sudo apt-get install jq # For yum: # $ sudo yum install jq access_token=$(curl --location --request POST 'https://api.port.io/v1/auth/access_token' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }' | jq '.accessToken' | sed 's/"//g') # The token will be available in the access_token variable blueprint_id='MY_BLUEPRINT' # Note the ?upsert=true query parameter curl --location --request POST "https://api.port.io/v1/blueprints/${blueprint_id}/entities?upsert=true" \ --header "Authorization: Bearer $access_token" \ --header "Content-Type: application/json" \ --data-raw '{ "identifier": "MY_ENTITY_IDENTIFIER", "title": "MY ENTITY TITLE", "properties": { "MY_STRING_PROP": "MY VALUE", "MY_BOOLEAN_PROP": false } }' # Use jq '.entity' to read the entity ``` * Python * Javascript * cURL ``` # Dependencies to install: # $ python -m pip install requests import requests CLIENT_ID = 'YOUR_CLIENT_ID' CLIENT_SECRET = 'YOUR_CLIENT_SECRET' API_URL = 'https://api.port.io/v1' credentials = {'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET} token_response = requests.post(f'{API_URL}/auth/access_token', json=credentials) access_token = token_response.json()['accessToken'] # You can now use the value in access_token when making further requests headers = { 'Authorization': f'Bearer {access_token}' } blueprint_id = 'MY_BLUEPRINT' entity_id = 'MY_ENTITY' response = requests.delete(f'{API_URL}/blueprints/{blueprint_id}/entities/{entity_id}', headers=headers) # response.json() is {"ok": true} ``` ``` // Dependencies to install: // $ npm install axios --save const axios = require("axios").default; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const API_URL = "https://api.port.io/v1"; (async () => { const response = await axios.post(`${API_URL}/auth/access_token`, { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, }); const accessToken = response.data.accessToken; // You can now use the value in accessToken when making further requests const blueprintId = "MY_BLUEPRINT"; const entityId = "MY_ENTITY"; const config = { headers: { Authorization: `Bearer ${accessToken}`, }, }; const deleteResponse = await axios.delete( `${API_URL}/blueprints/${blueprintId}/entities/${entityId}`, config ); // deleteResponse.data is {ok: true} })(); ``` ``` # Dependencies to install: # For apt: # $ sudo apt-get install jq # For yum: # $ sudo yum install jq access_token=$(curl --location --request POST 'https://api.port.io/v1/auth/access_token' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }' | jq '.accessToken' | sed 's/"//g') # The token will be available in the access_token variable blueprint_id='MY_BLUEPRINT' entity_id='MY_ENTITY' curl --location --request DELETE "https://api.port.io/v1/blueprints/${blueprint_id}/entities/${entity_id}" \ --header "Authorization: Bearer $access_token" # Response is {"ok": true} ``` It is possible to delete all entities of a blueprint with a single request using a dedicated route. It is also possible to delete the blueprint containing the entities within the same delete operation by adding the `delete_blueprint=true` query parameter, for example: `https://api.port.io/v1/blueprints//all-entities?delete_blueprint=true`. limitation The delete all route can only be used if the blueprint does not have required relations. * Python * Javascript * cURL ``` # Dependencies to install: # $ python -m pip install requests import requests CLIENT_ID = 'YOUR_CLIENT_ID' CLIENT_SECRET = 'YOUR_CLIENT_SECRET' API_URL = 'https://api.port.io/v1' credentials = {'clientId': CLIENT_ID, 'clientSecret': CLIENT_SECRET} token_response = requests.post(f'{API_URL}/auth/access_token', json=credentials) access_token = token_response.json()['accessToken'] # You can now use the value in access_token when making further requests headers = { 'Authorization': f'Bearer {access_token}' } blueprint_id = 'MY_BLUEPRINT' response = requests.delete(f'{API_URL}/blueprints/{blueprint_id}/all-entities', headers=headers) # response.json() is {"ok": true} ``` ``` // Dependencies to install: // $ npm install axios --save const axios = require("axios").default; const CLIENT_ID = "YOUR_CLIENT_ID"; const CLIENT_SECRET = "YOUR_CLIENT_SECRET"; const API_URL = "https://api.port.io/v1"; (async () => { const response = await axios.post(`${API_URL}/auth/access_token`, { clientId: CLIENT_ID, clientSecret: CLIENT_SECRET, }); const accessToken = response.data.accessToken; // You can now use the value in accessToken when making further requests const blueprintId = "MY_BLUEPRINT"; const config = { headers: { Authorization: `Bearer ${accessToken}`, }, }; const deleteResponse = await axios.delete( `${API_URL}/blueprints/${blueprintId}/all-entities`, config ); // deleteResponse.data is {ok: true} })(); ``` ``` # Dependencies to install: # For apt: # $ sudo apt-get install jq # For yum: # $ sudo yum install jq access_token=$(curl --location --request POST 'https://api.port.io/v1/auth/access_token' \ --header 'Content-Type: application/json' \ --data-raw '{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }' | jq '.accessToken' | sed 's/"//g') # The token will be available in the access_token variable blueprint_id='MY_BLUEPRINT' curl --location --request DELETE "https://api.port.io/v1/blueprints/${blueprint_id}/all-entities" \ --header "Authorization: Bearer $access_token" # Response is {"ok": true} ``` Delete all entities via the UI It is also possible to delete all entities using Port's web UI: 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on the "Delete All `BLUEPRINT_NAME`" button on the desired blueprint. 3. Follow the instructions. **Note:** only users with the [admin](/platform-administration/users-and-teams/manage-users-teams/.md#roles--permissions) role can use Port's UI to perform the delete all operation. Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Rate limits[​](#rate-limits "Direct link to Rate limits") For more information about Port's rate limits and how to avoid them, check out the [rate limits page](/api-reference/rate-limits/). --- # Implement an API client The API client is the only place that should talk to the third-party system. Keep authentication, pagination, and rate limiting there, and call the client from resync handlers and live event processors. ## Use Ocean's async HTTP client[​](#use-oceans-async-http-client "Direct link to Use Ocean's async HTTP client") Prefer `http_async_client` from Ocean instead of `requests` or a new `httpx.AsyncClient`. Ocean's client is shared, async-friendly, and aligned with Ocean's retries, timeouts, and logging. ``` from httpx import Timeout from port_ocean.context.ocean import ocean from port_ocean.utils import http_async_client class MyClient: def __init__(self) -> None: self.base_url = ocean.integration_config["apiBaseUrl"].rstrip("/") self.client = http_async_client self.client.headers["Authorization"] = ( f"Bearer {ocean.integration_config['apiToken']}" ) self.client.timeout = Timeout(30) ``` Read secrets and settings from `ocean.integration_config`. Keys match the camelCase `name` values in `.port/spec.yaml`. ## Pagination and batching[​](#pagination-and-batching "Direct link to Pagination and batching") Fetch pages from the third-party API and yield batches of raw objects (lists of dicts). Do not load the entire dataset into memory when the API supports pagination. ``` from typing import Any, AsyncGenerator async def get_paginated_items(self) -> AsyncGenerator[list[dict[str, Any]], None]: url = f"{self.base_url}/items" params: dict[str, Any] = {"limit": 100} while url: response = await self.client.get(url, params=params) response.raise_for_status() body = response.json() yield body["items"] url = body.get("next") params = {} ``` ## Practices[​](#practices "Direct link to Practices") * Keep I/O async so you do not block Ocean's event loop. * Centralize request helpers so auth headers and error handling stay consistent. * If you register webhooks in the third-party system on startup, put that logic on the client and call it from `@ocean.on_start()`. * Return API payloads as close to the source as practical. Enrich with custom fields only under keys that start with `__` (for example `__team_id`) so they do not collide with upstream fields. ## Next steps[​](#next-steps "Direct link to Next steps") * [Handle resyncs](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/resyncs/.md). * [Development guidelines](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/guidelines/.md). --- # Define configuration files Ocean loads resources from the `.port` directory when the integration starts. The scaffold creates this layout: ``` my-integration/ └── .port/ ├── spec.yaml └── resources/ ├── blueprints.json └── port-app-config.yaml ``` | File | Required | Role | | -------------------------------- | -------- | ------------------------------------------------------------------------------------------- | | `spec.yaml` | Yes | Integration type, supported kinds, user config schema, and features. | | `resources/blueprints.json` | No | Default blueprints created or updated on start (when `initializePortResources` is enabled). | | `resources/port-app-config.yaml` | No | Default [mapping](/context-lake/ingestion/configure-mapping/overview/.md) for those kinds. | File names Scaffolds may use `.yaml` or `.yml`. Use the names your scaffold generated. ## `spec.yaml`[​](#specyaml "Direct link to specyaml") `spec.yaml` declares how Port presents the integration and which parameters users must provide. Typical sections: .port/spec.yaml ``` type: myIntegration description: My integration for Port icon: Default features: - type: exporter section: Other resources: - kind: my-kind configurations: - name: apiBaseUrl required: true type: url description: Base URL of the third-party API - name: apiToken required: true type: string sensitive: true description: API token for the third-party API ``` | Field | Purpose | | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | Integration type identifier (also used for image naming conventions). | | `features[].resources` | Kinds your resync handlers support. | | `configurations` | User-facing parameters. Validated at startup. See [configuration validation](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/configuration-validation/.md). | For each `configurations` entry, set `name` (camelCase), `required`, `type` (`string`, `integer`, `boolean`, `object`, `url`, and related types), `description`, and `sensitive` when the value is a secret. ## Default blueprints and mapping[​](#default-blueprints-and-mapping "Direct link to Default blueprints and mapping") `blueprints.json` and `port-app-config.yaml` are optional. When present and `initializePortResources` is `true` (default), Ocean creates or updates them in Port on start. Author mapping so that: * Each `kind` matches a `@ocean.on_resync` handler (or a live event kind you support). * JQ expressions read fields from the **raw** objects your code yields. * Users can later edit the mapping in Port without changing your integration code. See [configure mapping](/context-lake/ingestion/configure-mapping/overview/.md) for mapping structure and JQ patterns. Changing defaults after install After the first install, changing `.port/resources/` on disk does not always overwrite what is already in Port. Update blueprints and mapping in the Port UI or API when the integration is already installed. ## Next steps[​](#next-steps "Direct link to Next steps") * [Implement an API client](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/api-client/.md). * [Kinds and selectors](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/kinds-and-selectors/.md) if you need custom mapping fields. --- # Configuration validation Ocean validates integration configuration against `.port/spec.yaml` before the integration starts its work. Invalid types or missing required fields prevent a bad config from running mid-sync. Declare parameters under `configurations` in `spec.yaml`. See [define configuration files](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/configuration-files/.md). ## Supported types[​](#supported-types "Direct link to Supported types") | Type | Example value | | --------- | ------------------------- | | `string` | `my-token` | | `integer` | `123` | | `boolean` | `true` | | `object` | `{"key": "value"}` | | `url` | `https://api.example.com` | .port/spec.yaml ``` configurations: - name: apiBaseUrl type: url required: true description: Base URL of the third-party API - name: enableFeature type: boolean required: false description: Toggle an optional feature ``` If a user sets `enableFeature` to a non-boolean value, Ocean rejects the configuration at startup. Values arrive in your code through `ocean.integration_config` after validation. For local runs, set the matching `OCEAN__INTEGRATION__CONFIG__*` environment variables as described in [test and run locally](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/testing/.md). --- # Ocean and event contexts Ocean exposes two context objects you can import in integration code. ## Ocean context[​](#ocean-context "Direct link to Ocean context") Import `ocean` from `port_ocean.context.ocean`. Common attributes and helpers: | Access | Purpose | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `ocean.integration_config` | User configuration from `spec.yaml` / env (camelCase keys). | | `ocean.config` | Full Ocean settings object. | | `ocean.port_client` | Client Ocean uses to talk to Port's API. | | `ocean.router` | FastAPI router for custom HTTP routes under the integration. | | `ocean.on_resync` | Decorator to register resync handlers. | | `ocean.on_start` | Decorator for startup hooks. | | `ocean.on_probe` | Decorator for [connection probe](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/connection-probe/.md) checks. | | `ocean.add_webhook_processor` | Register a live event processor. | | `ocean.register_action_executor` | Register an [action](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/actions-processing/.md) executor. | | `ocean.event_listener_type` | Active listener type (for example `POLLING` or `ONCE`). | ``` from port_ocean.context.ocean import ocean from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE @ocean.on_resync("project") async def on_resync_projects(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: base_url = ocean.integration_config["apiBaseUrl"] ... ``` ## Event context[​](#event-context "Direct link to Event context") Import `event` from `port_ocean.context.event`. It is available only while Ocean is handling an event (resync, start, or HTTP request). | Access | Purpose | | ----------------------- | ------------------------------------------------------------------------- | | `event.type` | Event type (for example `resync`, `start`, `http_request`). | | `event.trigger_type` | How the event was triggered (for example `manual`, `machine`, `request`). | | `event.resource_config` | Mapping entry for the current kind during a resync. | | `event.port_app_config` | Full Port app config for the resync. | | `event.attributes` | Per-event dict for caching shared lookups during that event. | ``` from port_ocean.context.event import event resource_config = event.resource_config event_type = event.type ``` --- # Deploy a custom-coded Ocean integration Scaffolds include a `Dockerfile`. Build your own image, then run it with Docker or deploy it with the Ocean Helm chart using that image. ## Build a Docker image[​](#build-a-docker-image "Direct link to Build a Docker image") From the integration directory (adjust the tag as needed): ``` docker build -t my-custom-coded-ocean-integration:latest . ``` The scaffold Dockerfile installs Poetry dependencies and runs the Ocean process. Keep secrets out of the image. Pass Port credentials and integration config as environment variables or Kubernetes secrets at runtime. ### Run with Docker[​](#run-with-docker "Direct link to Run with Docker") ``` docker run --rm \ -e OCEAN__PORT__CLIENT_ID="" \ -e OCEAN__PORT__CLIENT_SECRET="" \ -e OCEAN__INTEGRATION__IDENTIFIER="my-private-integration" \ -e OCEAN__INTEGRATION__TYPE="myIntegration" \ -e OCEAN__EVENT_LISTENER__TYPE="POLLING" \ -e OCEAN__INTEGRATION__CONFIG__API_BASE_URL="https://api.example.com" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="" \ -p 8000:8000 \ my-custom-coded-ocean-integration:latest ``` Use the same env var naming as in [test and run locally](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/testing/.md). ## Deploy with Helm[​](#deploy-with-helm "Direct link to Deploy with Helm") 1. Push your image to a registry your cluster can pull. 2. Add the Port Helm repository: ``` helm repo add port-labs https://port-labs.github.io/helm-charts helm repo update ``` 3. Install the `port-ocean` chart and point it at your image. The chart uses `imageRegistry` plus `image` (repository path and tag, without the registry prefix): ``` helm upgrade --install my-private-integration port-labs/port-ocean \ --set port.clientId="" \ --set port.clientSecret="" \ --set initializePortResources=true \ --set integration.identifier="my-private-integration" \ --set integration.type="myIntegration" \ --set integration.eventListener.type="POLLING" \ --set imageRegistry="YOUR_REGISTRY" \ --set image="my-custom-coded-ocean-integration:latest" \ --set integration.secrets.apiToken="" \ --set integration.config.apiBaseUrl="https://api.example.com" ``` See the [port-ocean chart values](https://github.com/port-labs/helm-charts/blob/main/charts/port-ocean/values.yaml) for the full list of knobs. For listener choices, scheduled resync, proxies, TLS, and health endpoints, use: * [Event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md). * [Advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## After deploy[​](#after-deploy "Direct link to After deploy") Confirm the integration appears under [Connectors](https://app.port.io/settings/data-sources), that a resync completes, and that entities match your [mapping](/context-lake/ingestion/configure-mapping/overview/.md). Use the integration's sync status and event log when troubleshooting. --- # Development guidelines ## Stay mapping-agnostic[​](#stay-mapping-agnostic "Direct link to Stay mapping-agnostic") Yield raw third-party objects. Let users shape catalog entities through [mapping](/context-lake/ingestion/configure-mapping/overview/.md). If you add computed fields, prefix them with `__` (for example `__owners`) so they do not collide with upstream API fields. ## Use async HTTP[​](#use-async-http "Direct link to Use async HTTP") Use `http_async_client` from Ocean. Avoid synchronous `requests` calls on the event loop. See [implement an API client](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/api-client/.md). ## Stream with generators[​](#stream-with-generators "Direct link to Stream with generators") In `@ocean.on_resync` handlers, `yield` batches instead of returning one giant list. That keeps memory stable and lets Ocean process data as it arrives. ``` @ocean.on_resync("project") async def on_resync_projects(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: client = MyClient() async for batch in client.get_paginated_projects(): yield batch ``` ## Logging[​](#logging "Direct link to Logging") Use `loguru`: ``` from loguru import logger logger.info("Fetched batch of {} projects", len(projects)) ``` ## Live events[​](#live-events "Direct link to Live events") Do not treat the webhook payload as the same shape as a resync response unless the third-party API guarantees that. Fetch the current resource in `handle_event` and return that raw object (or a delete). See [implement live events](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/live-events/.md). ## Configuration access[​](#configuration-access "Direct link to Configuration access") Read user settings from `ocean.integration_config` (camelCase keys from `spec.yaml`). During a resync, use `event.resource_config` for per-kind selectors. ## Lint and types[​](#lint-and-types "Direct link to Lint and types") Run `make lint` (or your scaffold's equivalent) before you deploy. Keep type hints on public handlers and client methods. ## Related runtime docs[​](#related-runtime-docs "Direct link to Related runtime docs") * [Event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md). * [Actions processing](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/actions-processing/.md) if you register action executors. * [Advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md) for proxies, TLS, and base URL. --- # Implement incremental sync [Incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync/.md) runs on a short interval in parallel to the full resync. It fetches only objects created or updated since the last run for the kinds you opt in. Your job is to register `@ocean.on_incremental_resync` handlers that use the active cursor when calling the third-party API, and yield batches of raw objects (same shape as a full resync). Port applies your [mapping](/context-lake/ingestion/configure-mapping/overview/.md) to those objects. Incremental sync does not run reconciliation - deletions still rely on a full resync. ## Register an incremental handler[​](#register-an-incremental-handler "Direct link to Register an incremental handler") Use `@ocean.on_incremental_resync` for each kind that can sync incrementally. You can stack it with `@ocean.on_resync` when the same function supports both modes: ``` from port_ocean.context.ocean import ocean from port_ocean.core.incremental.cursor_context import active_incremental_cursor from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE from client import MyClient from kinds import ObjectKind @ocean.on_incremental_resync(ObjectKind.ISSUE) @ocean.on_resync(ObjectKind.ISSUE) async def resync_issues(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: client = MyClient() cursor = active_incremental_cursor() async for batch in client.get_paginated_issues(updated_since=cursor): yield batch ``` `active_incremental_cursor()` returns a `datetime` during an incremental run, and `None` during a full resync. Pass that into your API client as a filter when it is set. Only kinds with an `@ocean.on_incremental_resync` handler participate in incremental sync. Other kinds in the mapping are skipped on incremental runs and continue to update via full resync (and live events, if you implemented them). ## Strategies[​](#strategies "Direct link to Strategies") Ocean provides helpers in `port_ocean.core.incremental.strategies` to turn a cursor into API parameters. ### Server-side filter[​](#server-side-filter "Direct link to Server-side filter") Use when the third-party API accepts a "changed since" query parameter: ``` from port_ocean.core.incremental.strategies import ( ServerSideTimestampStrategy, paginate_with_strategy, ) ISSUE_INCREMENTAL = ServerSideTimestampStrategy(param_key="since") @ocean.on_incremental_resync(ObjectKind.ISSUE) async def incremental_resync_issues(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: client = MyClient() cursor = active_incremental_cursor() params = ISSUE_INCREMENTAL.merge_params({"state": "all"}, cursor) async for batch in paginate_with_strategy( client.get_paginated_issues(params), cursor=cursor, strategy=ISSUE_INCREMENTAL, ): yield batch ``` ### Client-side cutoff[​](#client-side-cutoff "Direct link to Client-side cutoff") Use when the API has no time filter but can return items newest-first. Sort descending, yield items newer than the cursor, and stop paging when you hit older items: ``` from port_ocean.core.incremental.strategies import ( ClientSideCutoffStrategy, paginate_with_strategy, ) PR_INCREMENTAL = ClientSideCutoffStrategy( stop_field="updated_at", query_params={"sort": "updated", "direction": "desc"}, ) @ocean.on_incremental_resync(ObjectKind.PULL_REQUEST) async def incremental_resync_pull_requests( kind: str, ) -> ASYNC_GENERATOR_RESYNC_TYPE: client = MyClient() cursor = active_incremental_cursor() params = PR_INCREMENTAL.merge_params({}, cursor) async for batch in paginate_with_strategy( client.get_paginated_pull_requests(params), cursor=cursor, strategy=PR_INCREMENTAL, ): yield batch ``` ## Enable incremental sync[​](#enable-incremental-sync "Direct link to Enable incremental sync") For self-hosted and private deployments, set: ``` OCEAN__INTEGRATION__INCREMENTAL_SYNC_ENABLED=true OCEAN__INTEGRATION__INCREMENTAL_SYNC_INTERVAL=15 ``` `INCREMENTAL_SYNC_INTERVAL` must be `15`, `30`, or `60` (minutes). The default interval is 15. For Port-hosted integrations, enable incremental sync and set the interval under **Advanced configuration** on the [connectors](https://app.port.io/settings/data-sources) page. With a `ONCE` [event listener](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md), an enabled incremental sync runs an incremental pass instead of a full resync. ## Limitations to design for[​](#limitations-to-design-for "Direct link to Limitations to design for") * No reconciliation on incremental runs - design handlers to fetch creates and updates only. * The first incremental run looks back one interval - rely on a preceding full resync for older data. * Keep incremental raw shapes consistent with full resync shapes so the same mapping works for both. See [incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync/.md) for platform behavior and [sync interactions](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/sync-interactions/.md) for how incremental runs overlap with full resyncs and live events. --- # Kinds and selectors Kinds are the resource types your integration syncs (for example `project` or `issue`). Each kind can have a resync handler and, optionally, custom fields in the mapping YAML that control how you call the third-party API. When you can skip this page If every kind uses fixed API parameters and you do not need user-defined filters in the mapping, you can skip custom selectors. Declare kinds in `.port/spec.yaml` and implement `@ocean.on_resync` handlers only. ## Layers[​](#layers "Direct link to Layers") | Layer | Role | | -------------------------- | --------------------------------------------------------------------- | | `Selector` | User-facing fields for one kind (filters, expand flags, field lists). | | `ResourceConfig` | Binds a selector to a kind literal. | | `PortAppConfig` | Lists the resource configs your integration accepts. | | `BaseIntegration` subclass | Wires your `PortAppConfig` into Ocean via `integration.py`. | ## Example[​](#example "Direct link to Example") integration.py ``` from typing import Literal from port_ocean.core.handlers.port_app_config.api import APIPortAppConfig from port_ocean.core.handlers.port_app_config.models import ( PortAppConfig, ResourceConfig, Selector, ) from port_ocean.core.integrations.base import BaseIntegration from pydantic import Field class ProjectSelector(Selector): expand: str | None = Field( default=None, description="Optional expand parameter passed to the third-party API", ) class ProjectResourceConfig(ResourceConfig): kind: Literal["project"] selector: ProjectSelector class MyPortAppConfig(PortAppConfig): resources: list[ProjectResourceConfig | ResourceConfig] = Field( default_factory=list ) class MyIntegration(BaseIntegration): class AppConfigHandlerClass(APIPortAppConfig): CONFIG_CLASS = MyPortAppConfig ``` Users can then set the selector in mapping YAML: ``` resources: - kind: project selector: query: "true" expand: "description" port: entity: mappings: identifier: .id title: .name blueprint: '"project"' properties: {} ``` In the resync handler, read `event.resource_config` and pass selector values into your client. See [handle resyncs](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/resyncs/.md#use-selector-config-during-resync). ## Mapping reference[​](#mapping-reference "Direct link to Mapping reference") For entity mapping fields (`identifier`, `title`, `properties`, `relations`, `itemsToParse`, and related options), see [configure mapping](/context-lake/ingestion/configure-mapping/overview/.md). Your integration should keep yielding raw API objects; the mapping decides how those objects become catalog entities. --- # Implement live events Live events keep the catalog up to date between resyncs. In code they are webhook processors: HTTP `POST` handlers that authenticate the request, decide which kinds are affected, and return updated or deleted **raw** objects. For how Ocean queues, routes, and retries live events at runtime, see [live events processing](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/live-events-processing/.md). ## Processor interface[​](#processor-interface "Direct link to Processor interface") Subclass `AbstractWebhookProcessor` and implement: | Method | Purpose | | ---------------------- | ---------------------------------------------------- | | `should_process_event` | Return whether this processor handles the event. | | `get_matching_kinds` | Return the kinds the event affects. | | `authenticate` | Verify the request (signature, token, and similar). | | `validate_payload` | Reject malformed payloads. | | `handle_event` | Fetch current data if needed and return raw results. | `handle_event` must return `WebhookEventRawResults` with: * `updated_raw_results` - raw objects to upsert via mapping. * `deleted_raw_results` - raw objects that identify deletes. Prefer fetching the latest resource from the third-party API instead of registering the webhook payload as-is. Webhook bodies are often partial and can diverge from resync shapes. ## Example[​](#example "Direct link to Example") ``` from port_ocean.core.handlers.port_app_config.models import ResourceConfig from port_ocean.core.handlers.webhook.abstract_webhook_processor import ( AbstractWebhookProcessor, ) from port_ocean.core.handlers.webhook.webhook_event import ( EventHeaders, EventPayload, WebhookEvent, WebhookEventRawResults, ) from client import MyClient from kinds import ObjectKind class ProjectWebhookProcessor(AbstractWebhookProcessor): async def should_process_event(self, event: WebhookEvent) -> bool: return event.payload.get("eventType", "").startswith("project.") async def get_matching_kinds(self, event: WebhookEvent) -> list[str]: return [ObjectKind.PROJECT] async def authenticate( self, payload: EventPayload, headers: EventHeaders ) -> bool: return True async def validate_payload(self, payload: EventPayload) -> bool: return "project" in payload async def handle_event( self, payload: EventPayload, resource_config: ResourceConfig ) -> WebhookEventRawResults: client = MyClient() project_id = payload["project"]["id"] if payload.get("eventType") == "project.deleted": return WebhookEventRawResults( updated_raw_results=[], deleted_raw_results=[payload["project"]], ) project = await client.get_project(project_id) if not project: return WebhookEventRawResults( updated_raw_results=[], deleted_raw_results=[payload["project"]], ) return WebhookEventRawResults( updated_raw_results=[project], deleted_raw_results=[], ) ``` ## Register processors[​](#register-processors "Direct link to Register processors") Register processors on a fixed path under the integration's `/integration` prefix (for example `/webhook`): main.py ``` from port_ocean.context.ocean import ocean from webhook_processors.project_webhook_processor import ProjectWebhookProcessor ocean.add_webhook_processor("/webhook", ProjectWebhookProcessor) ``` Register the matching webhook URL in the third-party system from `@ocean.on_start()`, using your public base URL. See [advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md) for base URL and path prefix settings. Kinds must be in the mapping Ocean processes live events only for kinds that exist in the integration's mapping. If you remove a kind from the mapping, live events for it are ignored. --- # Write your own Ocean integration After [scaffolding](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/getting-started/.md), implement the third-party logic, configuration, and deployment for your custom-coded Ocean integration. Your code should return **raw** objects from the third-party API. Keep those objects mapping-agnostic so users can change how data appears in Port without changing your Python code. Port applies the [mapping configuration](/context-lake/ingestion/configure-mapping/overview/.md) and updates the catalog. ## Build flow[​](#build-flow "Direct link to Build flow") 1. [Define configuration files](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/configuration-files/.md) - `.port/spec.yaml`, optional blueprints and default mapping. 2. [Implement an API client](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/api-client/.md) - authenticate and paginate with Ocean's async HTTP client. 3. [Handle resyncs](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/resyncs/.md) - `@ocean.on_resync` handlers that yield batches of raw data. 4. [Implement incremental sync](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/incremental-sync/.md) - optional `@ocean.on_incremental_resync` handlers for changed-since fetches. 5. [Kinds and selectors](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/kinds-and-selectors/.md) - optional custom mapping fields in `integration.py`. 6. [Implement live events](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/live-events/.md) - optional webhook processors. 7. [Test and run locally](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/testing/.md) - env vars, `make run`, and resync triggers. 8. [Development guidelines](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/guidelines/.md) - httpx, logging, performance, and raw-data practices. 9. [Deploy](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/deploy/.md) - Docker and Helm with your private image. ## Reference[​](#reference "Direct link to Reference") * [Ocean and event contexts](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/contexts/.md) - `ocean` and `event` globals. * [Configuration validation](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/configuration-validation/.md) - types in `spec.yaml`. * [Ocean framework](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/overview/.md) - event listeners, live events runtime, actions, probe, and advanced configuration. --- # Handle resyncs A resync asks your integration to fetch current data for one or more kinds. You implement that with `@ocean.on_resync` handlers in `main.py`. Ocean calls your handler for each matching kind in the mapping. You yield batches of raw objects. Port applies the [mapping](/context-lake/ingestion/configure-mapping/overview/.md) and updates the catalog. For when resyncs are triggered and which listener types exist, see [event listeners and resync triggers](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md). ## Resync handler pattern[​](#resync-handler-pattern "Direct link to Resync handler pattern") main.py ``` from port_ocean.context.ocean import ocean from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE from client import MyClient @ocean.on_resync("project") async def on_resync_projects(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: client = MyClient() async for projects in client.get_paginated_projects(): yield projects ``` Requirements: * The `kind` string matches a kind in your mapping and in `spec.yaml`. * Each `yield` is a **list of objects** (dicts), not a single object. * Prefer async generators so Ocean can stream batches as you fetch them. ## Kind-specific handlers[​](#kind-specific-handlers "Direct link to Kind-specific handlers") Use an enum for kind names and register one handler per kind (or a generic handler when the client API is uniform): ``` from enum import StrEnum from port_ocean.context.event import event from port_ocean.context.ocean import ocean from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE class ObjectKind(StrEnum): PROJECT = "project" ISSUE = "issue" @ocean.on_resync(ObjectKind.PROJECT) async def on_resync_projects(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: client = MyClient() async for batch in client.get_paginated_projects(): yield batch ``` ## Use selector config during resync[​](#use-selector-config-during-resync "Direct link to Use selector config during resync") If you defined custom selectors in `integration.py`, read them from `event.resource_config` while the resync runs: ``` from typing import cast from port_ocean.context.event import event from integration import ProjectResourceConfig @ocean.on_resync(ObjectKind.PROJECT) async def on_resync_projects(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: client = MyClient() selector = cast(ProjectResourceConfig, event.resource_config).selector async for batch in client.get_paginated_projects(expand=selector.expand): yield batch ``` See [kinds and selectors](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/kinds-and-selectors/.md). ## Startup hook[​](#startup-hook "Direct link to Startup hook") Use `@ocean.on_start()` for one-time setup, such as registering webhooks in the third-party system. Skip webhook registration when the listener is `ONCE` (CI-style single resync). ``` @ocean.on_start() async def on_start() -> None: if ocean.event_listener_type == "ONCE": return await MyClient().ensure_webhook() ``` ## Next steps[​](#next-steps "Direct link to Next steps") * [Implement incremental sync](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/incremental-sync/.md). * [Implement live events](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/live-events/.md). * [Test and run locally](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/testing/.md). --- # Test and run locally ## Map configuration to environment variables[​](#map-configuration-to-environment-variables "Direct link to Map configuration to environment variables") Parameters in `.port/spec.yaml` become environment variables with this pattern: * Port credentials: `OCEAN__PORT__CLIENT_ID`, `OCEAN__PORT__CLIENT_SECRET` (and optional `OCEAN__PORT__BASE_URL`). * Integration config: `OCEAN__INTEGRATION__CONFIG__`. Example: `apiBaseUrl` in `spec.yaml` maps to `OCEAN__INTEGRATION__CONFIG__API_BASE_URL`. .env ``` OCEAN__PORT__CLIENT_ID= OCEAN__PORT__CLIENT_SECRET= OCEAN__INTEGRATION__CONFIG__API_BASE_URL=https://api.example.com OCEAN__INTEGRATION__CONFIG__API_TOKEN= OCEAN__EVENT_LISTENER__TYPE=POLLING ``` Find Port credentials in [Find your Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials). ## Run the integration[​](#run-the-integration "Direct link to Run the integration") ``` cd ./my_integration make install source .venv/bin/activate cp .env.example .env # edit .env make run ``` `make run` (or `ocean sail`) starts the integration, applies default resources when enabled, and begins listening for resync requests. ## Trigger a resync[​](#trigger-a-resync "Direct link to Trigger a resync") While the integration runs with `POLLING` (default): * Open [Connectors](https://app.port.io/settings/data-sources), select the integration, and click **Resync**. * Or change the mapping or configuration in Port so the next poll detects a change. See [event listeners and resync triggers](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) for scheduled resync and other listener types. ## Debug[​](#debug "Direct link to Debug") * Set `APPLICATION__LOG_LEVEL=DEBUG` in `.env` for verbose logs. * Use the scaffolded `debug.py` with your IDE debugger if needed. ## Connection probe[​](#connection-probe "Direct link to Connection probe") To validate credentials and permissions without a full resync, implement `@ocean.on_probe()` and run `ocean probe`. See [connection probe](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/connection-probe/.md). ## Next steps[​](#next-steps "Direct link to Next steps") * [Deploy a custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/deploy/.md). * [Development guidelines](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/guidelines/.md). --- # Getting started Let's install the Ocean CLI, scaffold a custom-coded Ocean integration, and run it against your Port organization. ## Requirements[​](#requirements "Direct link to Requirements") * Python 3.12 or later. * [Port API credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) (client ID and client secret). ## Install the Ocean CLI[​](#install-the-ocean-cli "Direct link to Install the Ocean CLI") ``` python -m venv .venv source .venv/bin/activate pip install "port-ocean[cli]" ``` ## Scaffold a custom-coded Ocean integration[​](#scaffold-a-custom-coded-ocean-integration "Direct link to Scaffold a custom-coded Ocean integration") `ocean new` scaffolds a custom-coded Ocean integration by default (an integration you maintain outside the Ocean monorepo, including a `Dockerfile`). ``` ocean new ``` The CLI prompts for the integration name, description, your Port credentials, and region. Accept the default custom-coded Ocean integration option unless you intend to contribute to the [Ocean monorepo](https://github.com/port-labs/ocean). After scaffolding: ``` cd ./ make install source .venv/bin/activate cp .env.example .env ``` Update `.env` with your Port credentials and any integration-specific settings from `.port/spec.yaml`. See [test and run locally](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/testing/.md) for the environment variable naming convention. ## Project structure[​](#project-structure "Direct link to Project structure") A private scaffold typically looks like this: ``` my-integration/ ├── Dockerfile ├── pyproject.toml ├── Makefile ├── main.py ├── .env.example ├── debug.py ├── tests/ └── .port/ ├── spec.yaml └── resources/ ├── blueprints.json └── port-app-config.yaml ``` | Path | Role | | ------------------ | -------------------------------------------------------------------------------- | | `main.py` | Resync handlers, startup hooks, and live event / action registration. | | `.port/spec.yaml` | Integration type, supported kinds, and user-facing config schema. | | `.port/resources/` | Default blueprints and mapping created when the integration starts (if enabled). | | `Dockerfile` | Image build for private deployment. | | `Makefile` | `make install`, `make run`, and lint helpers. | ## Run locally[​](#run-locally "Direct link to Run locally") ``` make run ``` You can also run `ocean sail` from the integration directory. Ocean connects to Port, creates default resources if configured, starts the [event listener](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) (default `POLLING`), and runs a resync when needed. Interactive API docs While the integration is running, open `http://localhost:8000/docs` for the built-in Swagger UI, or `http://localhost:8000/redoc` for ReDoc. ## Next steps[​](#next-steps "Direct link to Next steps") * [Write your own Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/overview/.md) - replace the sample resync logic with your API client, kinds, and deploy. --- # Custom-coded Ocean integration If the [no-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) does not meet your needs, you can build a custom-coded Ocean integration in Python using Port's open-source [Ocean](https://github.com/port-labs/ocean) framework. Your integration's job is to **extract raw data** from a third-party system (and optionally handle live events and actions). You also author the [mapping configuration](/context-lake/ingestion/configure-mapping/overview/.md) that describes how that raw data becomes Port entities. Port applies the mapping and updates the catalog. For how Ocean runs once installed (event listeners, live events, actions, and more), see the [Ocean framework](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/overview/.md) section. ## No-code vs custom-coded[​](#no-code-vs-custom-coded "Direct link to No-code vs custom-coded") | | [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) | Custom-coded Ocean integration | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | | **How you build it** | Configuration only (endpoints, auth, JQ mapping). | Python code with the Ocean framework. | | **Best for** | REST APIs that return JSON over HTTP. | Complex auth, pagination, nested data, or non-standard APIs. | | **Live events** | Currently not supported. | Full control via Ocean event processors. | | **Maintenance** | Port hosts the runtime (or you run the generic custom image). | You own the app, image, and deployment. | Start with the no-code option when it covers your API. Choose custom-coded when you need logic that configuration alone cannot express. ## When to build a custom-coded Ocean integration[​](#when-to-build-a-custom-coded-ocean-integration "Direct link to When to build a custom-coded Ocean integration") Choose a custom-coded Ocean integration when: * You need custom authentication flows (OAuth2, JWT refresh, and similar). * The API has complex pagination, nested data, or kind-specific selectors. * You need live events that the no-code Ocean custom integration cannot express. * You want a dedicated Python app for one system, maintained outside the Ocean monorepo. If you only need to call a REST API with config and JQ mapping, start with the [no-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) instead. ## What you implement[​](#what-you-implement "Direct link to What you implement") | Responsibility | Where | | ------------------------------ | --------------------------------------------------------------------- | | Fetch raw data per `kind` | `@ocean.on_resync()` in `main.py`. | | Optional incremental sync | `@ocean.on_incremental_resync()` handlers that use the active cursor. | | Optional live events | Live event processors that return updated or deleted **raw** objects. | | Optional actions | Action executors registered with Ocean. | | Integration config schema | `.port/spec.yaml`. | | Default blueprints and mapping | `.port/resources/`. | | Custom mapping selectors | Optional `integration.py` classes. | Ocean core handles connecting to Port, listening for resync requests, receiving webhooks, and reporting sync status. See [how an Ocean integration runs](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/overview/.md#how-an-ocean-integration-runs). ## Build path[​](#build-path "Direct link to Build path") 1. [Getting started](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/getting-started/.md) - install the CLI, scaffold, and run locally. 2. [Write your own Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/build/overview/.md) - config, API client, resyncs, incremental sync, live events, testing, and deploy. ## Contribute publicly[​](#contribute-publicly "Direct link to Contribute publicly") To share an integration with the Port community, develop it in the [Ocean monorepo](https://github.com/port-labs/ocean) and follow the repository [contributing guide](https://github.com/port-labs/ocean/blob/main/CONTRIBUTING.md). Scaffolding with `ocean new` is the default for org-specific integrations. --- # Configuration In this page we will cover the configuration steps that apply to both installation methods (hosted by Port and self-hosted). We will walk through the prerequisites you need to gather, how to configure resource mapping, and advanced configuration options for complex scenarios. ## How it works[​](#how-it-works "Direct link to How it works") The No-code Ocean custom integration uses a [**two-step setup**](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md): 1. **Installation** - Configure connection settings that apply to all API calls ([hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md) or [self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)). 2. **Resource mapping** - Define which endpoints to sync and how to map them to Port entities. ## Resource mapping[​](#resource-mapping "Direct link to Resource mapping") After installation, you define **which endpoints to sync** in your `port-app-config.yml` file (or using the integration's configuration in Port). This is where you map each API endpoint to Port entities - similar to how you've mapped GitHub repositories or Jira issues in other integrations. ### Endpoint-as-kind feature[​](#endpoint-as-kind-feature "Direct link to Endpoint-as-kind feature") The `kind` field is now the **endpoint path itself**! This provides better visibility in Port's UI, allowing you to: * ✅ Track each endpoint's sync status individually. * ✅ Debug mapping issues per endpoint. * ✅ Monitor data ingestion per API call. ### Example: Mapping two endpoints[​](#example-mapping-two-endpoints "Direct link to Example: Mapping two endpoints") ``` resources: # First endpoint: users - kind: /api/v1/users selector: query: 'true' # JQ filter - 'true' means sync all entities data_path: '.users' # Where to find the data array in the response query_params: # Optional: add query parameters to the API call active: "true" department: "engineering" port: entity: mappings: identifier: .id title: .name blueprint: '"user"' properties: email: .email department: .department active: .is_active created: .created_at # Second endpoint: projects - kind: /api/v1/projects selector: query: 'true' data_path: '.data.projects' # Nested data extraction query_params: status: "active" port: entity: mappings: identifier: .project_id title: .project_name blueprint: '"project"' properties: description: .description owner: .owner.email budget: .budget_amount created: .created_date ``` ### What each field does[​](#what-each-field-does "Direct link to What each field does") * **`kind`**: The API endpoint path (combined with your base URL). * **`selector.query`**: JQ filter to include/exclude entities (use `'true'` to sync all). * **`selector.data_path`**: JQ expression pointing to the array of items in the response (see [Data path extraction](#data-path-extraction) below). * **`selector.query_params`**: (Optional) Query parameters added to the URL. * **`selector.method`**: (Optional) HTTP method, defaults to `GET`. * **`port.entity.mappings`**: How to map API fields to Port entity properties. ### Data path extraction[​](#data-path-extraction "Direct link to Data path extraction") The `data_path` field tells the integration where to find the array of items in your API response. Use a [JQ expression](https://jqlang.org/manual/) to point to the data array. | Response structure | Example response | `data_path` value | | ------------------ | -------------------------------------------------- | ----------------------- | | Direct array | `[{"id": 1}, {"id": 2}]` | *(omit - not needed)* | | Single nested | `{"data": [{"id": 1}], "meta": {...}}` | `.data` | | Deeply nested | `{"response": {"users": {"items": [...]}}}` | `.response.users.items` | | Array flattening | `{"groups": [{"rules": [...]}, {"rules": [...]}]}` | `.groups[].rules[]` | Array flattening Use JQ's `[]` operator to flatten nested arrays. For example, `.groups[].rules[]` extracts all rules from all groups into a single flat array. ## Advanced configurations[​](#advanced-configurations "Direct link to Advanced configurations") Once you have the basics working, these features handle more complex scenarios. ### Nested endpoints[​](#nested-endpoints "Direct link to Nested endpoints") Fetch data from dynamic endpoints that depend on other resources. Use `.__` to access path parameter values in your entity mappings (e.g., `.__ticket_id` for a `{ticket_id}` parameter). data\_path in path parameters The `data_path` in `path_parameters` is not inherited from the parent kind's `data_path`. If not configured, it defaults to `.` (the root of the response). **Example: Tickets and comments** ``` resources: # Parent endpoint - kind: /api/tickets selector: query: 'true' data_path: '.tickets' port: entity: mappings: identifier: .id | tostring title: .subject blueprint: '"ticket"' # Nested endpoint - fetches comments for each ticket - kind: /api/tickets/{ticket_id}/comments selector: query: 'true' data_path: '.comments' path_parameters: ticket_id: endpoint: /api/tickets field: .id filter: 'true' port: entity: mappings: identifier: .id | tostring blueprint: '"comment"' properties: content: .body ticketId: .__ticket_id | tostring # Access path parameter value relations: ticket: .__ticket_id | tostring # Use for parent relation ``` The integration will: 1. Call `/api/tickets` → Get ticket IDs \[101, 102, 103]. 2. Call `/api/tickets/101/comments`, `/api/tickets/102/comments`, etc. 3. Sync all comments with relations to their parent tickets. **More examples:** * `/projects/{project_id}/tasks` → Access `.__project_id` in task mappings. * `/repositories/{repo_id}/pull-requests` → Access `.__repo_id` in PR mappings. ### Pagination[​](#pagination "Direct link to Pagination") For APIs that split data across multiple pages, configure how the integration fetches all pages. Where pagination is configured Pagination settings (`paginationType`, `lastPagePath`, `cursorPath`, etc.) are applied at the **integration level**, not inside the `resources` block of your `port-app-config.yml`. They are shared across all endpoints (kinds) defined in your resource mapping. * **Hosted by Port:** set them in the **Pagination settings** section when configuring the integration (see [Pagination settings](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md#pagination-settings)). * **Self-hosted:** pass them via Helm values such as `integration.config.paginationType` (see [Configuration parameters](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md#configuration-parameters)). The YAML snippets below show the field names and values to use in those locations. #### Pagination types[​](#pagination-types "Direct link to Pagination types") **Offset-based** (like SQL): ``` GET /api/users?offset=0&limit=100 GET /api/users?offset=100&limit=100 ``` **Page-based** (traditional): ``` GET /api/users?page=1&size=100 GET /api/users?page=2&size=100 ``` **Cursor-based** (for large datasets): ``` GET /api/users?cursor=abc123&limit=100 GET /api/users?cursor=xyz789&limit=100 ``` **Next-link / URL-based** (OData APIs like Microsoft Graph): ``` GET /api/users?$top=100 GET ``` **Header-link / RFC 5988** (GitHub, GitLab, Bitbucket): ``` GET /api/users?per_page=100 Response header: Link: ; rel="next", ; rel="last" GET ``` #### Custom parameter names[​](#custom-parameter-names "Direct link to Custom parameter names") APIs often use different parameter names. You can configure: * **Pagination parameter:** Use `skip` instead of `offset`, or `after` instead of `cursor`. * **Size parameter:** Use `per_page` instead of `limit`, or `page_size` instead of `size`. * **Start page:** Specify if pages start at 0 or 1. **Example:** ``` # GitHub uses page/per_page paginationType: page paginationParam: page sizeParam: per_page startPage: 1 # Stripe uses limit/starting_after paginationType: cursor paginationParam: starting_after sizeParam: limit ``` #### Last page indicator[​](#last-page-indicator "Direct link to Last page indicator") Some page-based APIs such as Spring Data REST and Harness, set a boolean field to `true` on the last page instead of using a `has_more` field that evaluates to `false`. To handle this, you can use `lastPagePath` to specify this field using dot-notation. **Example API response:** ``` { "content": [...], "number": 0, "size": 100, "last": false } ``` **Configuration:** ``` paginationType: page lastPagePath: last startPage: 0 ``` For nested envelopes (e.g., `{"data": {"content": [...], "last": false}}`), use the full path: `lastPagePath: data.last`. Precedence with `hasMorePath` If both `hasMorePath` and `lastPagePath` are configured, `hasMorePath` takes precedence. If the configured field is missing from a response, pagination falls back to the default termination logic rather than stopping early. #### Cursor path configuration[​](#cursor-path-configuration "Direct link to Cursor path configuration") For cursor-based pagination, tell the integration where to find the next cursor in responses: **Example API response:** ``` { "data": [...], "meta": { "after_cursor": "xyz123", "has_more": true } } ``` **Configuration:** ``` cursorPath: meta.after_cursor hasMorePath: meta.has_more ``` #### Next-link path configuration[​](#next-link-path-configuration "Direct link to Next-link path configuration") For APIs that return a full URL for the next page (like Microsoft Graph's `@odata.nextLink`), use `next_link` pagination: **Example API response:** ``` { "value": [...], "@odata.nextLink": "https://graph.microsoft.com/v1.0/users?$top=100&page=2" } ``` **Configuration:** ``` paginationType: next_link nextLinkPath: "@odata.nextLink" ``` For APIs using different field names, configure `nextLinkPath` accordingly (e.g., `nextRecordsUrl` for Salesforce, `links.next` for nested paths). #### Header-link configuration[​](#header-link-configuration "Direct link to Header-link configuration") For APIs that return the next page URL in the HTTP `Link` header (RFC 5988), use `header_link` pagination. This is common with GitHub, GitLab, and Bitbucket APIs. **Example API response:** ``` HTTP/2 200 OK Content-Type: application/json Link: ; rel="next", ; rel="last" [{"id": 1, "name": "repo1"}, {"id": 2, "name": "repo2"}] ``` **Configuration:** ``` paginationType: header_link ``` Custom rel value Some APIs use a different `rel` value in the Link header. Configure `headerLinkRel` if your API uses something other than `next` (e.g., `nextPage`): ``` paginationType: header_link headerLinkRel: nextPage ``` ### Rate limiting[​](#rate-limiting "Direct link to Rate limiting") Control how the integration interacts with your API to prevent overwhelming it or hitting rate limits. #### Request timeout[​](#request-timeout "Direct link to Request timeout") How long to wait for each API call to complete. ``` timeout: 30 # seconds (default: 30) ``` **When to adjust:** * Increase for slow APIs or large responses (e.g., 60 seconds). * Decrease for fast, local APIs (e.g., 10 seconds). Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). --- # Hosted by Port Using this installation option means that the integration will be hosted by Port, with a customizable resync interval to ingest data into Port. Port manages the infrastructure, updates, and maintenance, allowing you to focus on configuring your API connection. ## Installation[​](#installation "Direct link to Installation") To install, follow these steps: 1. Go to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Click on the **+ Connector** button in the top-right corner. 3. Click on the **Custom** integration in the list. 4. Under **Select your installation method**, choose **Hosted by Port**. 5. Configure the **integration settings** and **application settings** as you wish (see below for details). ## Application settings[​](#application-settings "Direct link to Application settings") Every integration hosted by Port has the following customizable application settings, which are configurable after installation: * **Resync interval**: The frequency at which Port will ingest data from the integration. There are various options available, ranging from every 1 hour to once a day. If a sync is still in progress when the next one is due, the new sync will be skipped (up to 24 hours delay). This ensures that all kinds are fully synchronized and that cleanup of stale entities always takes place at the end of each sync. * **Send raw data examples**: A boolean toggle (**enabled** by default). If enabled, raw data examples will be sent from the integration to Port. These examples are used when [testing your mapping configuration](/context-lake/ingestion/configure-mapping/configuration-methods/.md#test-your-mapping---jq-playground), they allow you to run your `jq` expressions against real data and see the results. ## Integration settings[​](#integration-settings "Direct link to Integration settings") The following integration settings are available for the custom integration. Each setting has an ⓘ icon next to it in the UI, which you can hover over to see a description. ### Connection settings[​](#connection-settings "Direct link to Connection settings") | Setting | Description | Required | Default | | ------------- | --------------------------------------------------------------------- | -------- | ------- | | **Base Url** | The base URL of your HTTP API, for example: `https://api.example.com` | ✅ | - | | **Auth Type** | Authentication method: `none`, `bearer_token`, `basic`, or `api_key` | ❌ | `none` | ### Authentication settings[​](#authentication-settings "Direct link to Authentication settings") These settings are required based on your selected **Auth Type**: | Setting | Description | Required When | Default | | ------------------ | -------------------------------------- | ------------------------------- | ----------- | | **Api Token** | Bearer token for authentication | **Auth Type** is `bearer_token` | - | | **Username** | Username for basic authentication | **Auth Type** is `basic` | - | | **Password** | Password for basic authentication | **Auth Type** is `basic` | - | | **Api Key** | API key for authentication | **Auth Type** is `api_key` | - | | **Api Key Header** | Header name for API key authentication | **Auth Type** is `api_key` | `X-API-Key` | Port secrets Settings marked with sensitive data (such as tokens, passwords, and API keys) use [Port secrets](/platform-administration/secrets-management/port-secrets/.md) to ensure your sensitive data is encrypted and secure. When filling in such a setting, its value will be obscured (shown as `••••••••`). Port will automatically create a secret in your organization for each sensitive setting. ### Pagination settings[​](#pagination-settings "Direct link to Pagination settings") | Setting | Description | Required | Default | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------- | | **Pagination Type** | Pagination method: `none`, `offset`, `page`, or `cursor` | ❌ | `none` | | **Page Size** | Number of items per page for pagination | ❌ | `100` | | **Pagination Param** | Query parameter name for the main pagination value. For cursor: `cursor`, `page[after]`, `after`. For page: `page`, `pageNumber`. For offset: `offset`, `skip`. Defaults vary by paginationType. | ❌ | - | | **Size Param** | Query parameter name for page size/limit. Examples: `limit`, `size`, `page[size]`, `per_page`. Defaults vary by paginationType. | ❌ | - | | **Start Page** | Starting page number for page-based pagination (e.g., 0 or 1). Defaults to 1. | ❌ | `1` | | **Cursor Path** | Dot-notation path to extract cursor from response for cursor pagination (e.g., `meta.after_cursor`, `links.next`). Defaults to common patterns if not specified. | ❌ | - | | **Has More Path** | Dot-notation path to check if more pages exist (e.g., `meta.has_more`, `pagination.hasMore`). Works with all pagination types. Defaults to common patterns if not specified. | ❌ | - | | **Last Page Path** | Dot-notation path to a boolean field that is truthy on the final page (inverse of **Has More Path**). Useful for APIs like Spring Data REST that return `last: true` on the last page (e.g., `last`, `data.last`). If **Last Page Path** and **Has More Path** are set, **Has More Path** takes precedence. | ❌ | - | ### Performance settings[​](#performance-settings "Direct link to Performance settings") | Setting | Description | Required | Default | | --------------------------- | --------------------------------------------------------------------------------- | -------- | ------- | | **Timeout** | Request timeout in seconds | ❌ | `30` | | **Verify Ssl** | Whether to verify SSL certificates | ❌ | `true` | | **Max Concurrent Requests** | Maximum concurrent API requests (uses Ocean's built-in rate limiting and caching) | ❌ | `10` | ## Port source IP addresses[​](#port-source-ip-addresses "Direct link to Port source IP addresses") When using this installation method, Port will make outbound calls to your API from static IP addresses. You may need to add these addresses to your allowlist, in order to allow Port to interact with your API: * Europe (EU) * United States (US) ``` 54.73.167.226 63.33.143.237 54.76.185.219 ``` ``` 3.234.37.33 54.225.172.136 3.225.234.99 ``` ## More resources[​](#more-resources "Direct link to More resources") For all configuration options, code examples, and advanced use cases, check out the [No-code Ocean custom integration repository on GitHub](https://github.com/port-labs/ocean/tree/main/integrations/custom). --- # Build your integration This interactive guide will help you generate everything you need to connect your API to Port. **How it works:** 1. Configure your API connection settings 2. Choose an endpoint and select which fields to sync 3. Get your installation commands, blueprint, and mapping configuration *** ## Step 1: Configure your API[​](#step-1-configure-your-api "Direct link to Step 1: Configure your API") Set up the connection details for your API. These settings apply globally to all endpoints you'll sync from this API. **What you're configuring:** * **Base URL**: The root URL that all endpoint paths will be appended to * **Authentication**: How your API verifies requests (bearer token, API key, basic auth, or none) * **Pagination** (optional): How to fetch data across multiple pages if your API uses pagination * **Performance settings** (optional): Timeouts, concurrent requests, SSL verification Think of this as setting up the "connection" - these settings will be used for every API call the integration makes. **Base URL**https\://api.example.com**Authentication** \[x]Bearer Token\[ ]API Key\[ ]Basic Auth\[ ]None ••••••••••••••••• **Advanced Configuration** **Pagination (optional)** \[x]None\[ ]Offset/Limit\[ ]Page/Size\[ ]Cursor **Request Timeout (seconds)**30Maximum time to wait for API response (default: 30)**Max Concurrent Requests**10Maximum number of parallel API requests (default: 10)\[x]Verify SSL certificates *** ## Step 2: Choose what data to sync[​](#step-2-choose-what-data-to-sync "Direct link to Step 2: Choose what data to sync") Now that your API connection is configured, let's define what data to sync. This step helps you map a specific API endpoint to a Port blueprint. **What you'll do:** 1. **Specify the endpoint path** (e.g., `/api/v1/users`) that you want to sync 2. **Paste a sample API response** so we can detect the data structure 3. **Select the data path** - tell us where the array of items is in the response (e.g., `.data`, `.users`, or root array) 4. **Configure the blueprint** - give it an identifier and title 5. **Choose which fields to sync** - select the fields you want to ingest and mark which field is the unique identifier The builder will automatically detect field types (string, number, boolean, email, date, URL) from your sample response. ### 1️⃣ Enter Your Endpoint /api/v1/users ### 2️⃣ Paste API Response Test your endpoint (e.g., with `curl`) and paste a sample response here: { "data": \[ { "id": 1, "name": "John Doe", "email": "john\@example.com", "role": "admin" }, { "id": 2, "name": "Jane Smith", "email": "jane\@example.com", "role": "user" } ], "meta": { "total": 2, "page": 1 } } ### 3️⃣ Select Data Path **Select the array** that contains the items you want to sync to Port: \[ ]data2 items *** ## Step 3: Install and create in Port[​](#step-3-install-and-create-in-port "Direct link to Step 3: Install and create in Port") You're all set! Based on your configuration, we've generated everything you need: **What you'll get:** * **Installation commands** (Helm or Docker) with all your settings pre-configured * **Blueprint JSON** to create the data model in Port * **Mapping YAML** to configure which fields to sync Simply copy and run these in order to complete your integration setup. #### 📋 Complete Step 2 to generate your configuration You need to: * **Enter a Blueprint Identifier** (e.g., "user", "project") * **Select an Identifier field** (click "ID" button next to a field) Once complete, this section will show your installation commands, blueprint JSON, and mapping configuration. Port credentials needed Get your `PORT_CLIENT_ID` and `PORT_CLIENT_SECRET` from [Port Settings → Credentials](https://app.port.io/settings). --- # Custom authentication Self-hosted only Custom authentication is only available for self-hosted installations. It is not supported for integrations hosted by Port. The custom authentication feature enables you to configure OAuth2, JWT, and other dynamic token-based authentication flows beyond the standard authentication types (`bearer_token`, `basic`, `api_key`, `none`). ## Supported authentication methods[​](#supported-authentication-methods "Direct link to Supported authentication methods") The table below summarizes which authentication methods are supported by the no-code Ocean custom integration: | Method | Supported | Notes | | ------------------------------------------------- | --------- | ---------------------------------- | | Bearer token (static) | ✅ | Use `authType: bearer_token` | | API key (header) | ✅ | Use `authType: api_key` | | Basic auth | ✅ | Use `authType: basic` | | OAuth2 client credentials (`client_secret_basic`) | ✅ | Use custom auth with form body | | OAuth2 client credentials (`client_secret_post`) | ✅ | Use custom auth with JSON body | | JWT-based authentication | ✅ | Use custom auth with JWT endpoint | | Custom header authentication | ✅ | Use custom auth `headers` template | | OAuth2 `private_key_jwt` (RFC 7523) | ❌ | Not natively supported | | mTLS / certificate-based auth | ❌ | Not supported | private\_key\_jwt not supported The `private_key_jwt` client authentication method (RFC 7523 / JWT Bearer assertion) requires the client to sign JWT assertions with a private key. This is **not natively supported** by the no-code Ocean custom integration because: * The integration cannot generate signed JWTs dynamically. * Private key storage and management is not available. **Workaround:** If your API requires `private_key_jwt` authentication, consider using a proxy service that handles JWT signing. Your proxy can accept simple credentials from the Ocean integration and translate them into properly signed JWT assertions for the target API. ## How it works[​](#how-it-works "Direct link to How it works") When `authType` is set to `"custom"`, you configure two parts: 1. **How to authenticate** (`customAuthRequest`) - defines the authentication endpoint, HTTP method, headers, and body. 2. **How to use the token** (`customAuthRequestTemplate`) - defines where to inject authentication values in subsequent requests using template syntax. The integration automatically: * Authenticates on startup * Caches evaluated templates for performance * Refreshes tokens proactively (if `reauthenticateIntervalSeconds` is configured) or reactively on 401 errors * Handles concurrent requests safely with locking ## Configuration[​](#configuration "Direct link to Configuration") ### customAuthRequest[​](#customauthrequest "Direct link to customAuthRequest") Defines how to make the authentication request. This is configured as a JSON object. | Field | Type | Description | Required | | ------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `endpoint` | string | Authentication endpoint. Can be a relative path (e.g., `/oauth/token`) appended to your base URL, or a full URL (e.g., `https://auth.example.com/token`) | ✅ | | `method` | string | HTTP method for the authentication request. Allowed values: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. Default: `POST` | ❌ | | `headers` | object | HTTP headers to send with the authentication request (key-value pairs) | ❌ | | `body` | object | JSON request body for the authentication request. Mutually exclusive with `bodyForm` | ❌ | | `bodyForm` | string | Form-encoded request body (e.g., `grant_type=client_credentials&client_id=...`). Mutually exclusive with `body` | ❌ | | `queryParams` | object | Query parameters to send with the authentication request (key-value pairs) | ❌ | | `reauthenticateIntervalSeconds` | integer | Token validity duration in seconds. If provided, tokens refresh 60 seconds before expiration. If not provided, tokens only refresh on 401 errors. Must be >= 1 | ❌ | Body configuration You cannot specify both `body` and `bodyForm` in `customAuthRequest`. Use `body` for JSON payloads and `bodyForm` for form-encoded payloads. ### customAuthRequestTemplate[​](#customauthrequesttemplate "Direct link to customAuthRequestTemplate") Defines how to use the authentication response in subsequent API requests. Use template syntax `{{.jq_path}}` to extract values from the auth response. At least one field must be provided. | Field | Type | Description | Required | | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | | `headers` | object | HTTP headers to add or override in subsequent API requests. Use template syntax to inject values (e.g., `{"Authorization": "Bearer {{.access_token}}"}`) | ❌ | | `queryParams` | object | Query parameters to add or override in subsequent API requests. Use template syntax to inject values (e.g., `{"api_key": "{{.access_token}}"}`) | ❌ | | `body` | object | Request body fields to add or override in subsequent API requests. Merged with request body if present. Use template syntax to inject values (e.g., `{"token": "{{.accessToken}}"}`) | ❌ | #### Template syntax[​](#template-syntax "Direct link to Template syntax") Use `{{.jq_path}}` to extract values from the authentication response JSON. The path uses dot notation to access nested fields: * `{{.access_token}}` - Extracts the `access_token` field from the root * `{{.data.token}}` - Extracts the `token` field from a nested `data` object * `{{.nested.field.value}}` - Extracts deeply nested values The template syntax is validated at configuration time, so errors fail fast before runtime. ## Installation examples[​](#installation-examples "Direct link to Installation examples") ### Using Helm[​](#using-helm "Direct link to Using Helm") When using Helm, pass the custom authentication configuration as a JSON string in the `integration.config.customAuthRequest` and `integration.config.customAuthRequestTemplate` parameters: ``` helm install ocean-custom port-labs/port-ocean \ --set port.clientId="" \ --set port.clientSecret="" \ --set port.baseUrl="https://api.port.io" \ --set integration.identifier="ocean-custom" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://api.yourcompany.com" \ --set integration.config.authType="custom" \ --set-json integration.config.customAuthRequest='{"endpoint":"/oauth/token","method":"POST","headers":{"Content-Type":"application/x-www-form-urlencoded"},"bodyForm":"grant_type=client_credentials&client_id=&client_secret=","reauthenticateIntervalSeconds":3600}' \ --set-json integration.config.customAuthRequestTemplate='{"headers":{"Authorization":"Bearer {{.access_token}}"}}' ``` ### Using Docker[​](#using-docker "Direct link to Using Docker") When using Docker, pass the configuration as JSON strings in environment variables: ``` docker run -i --rm \ -e OCEAN__PORT__CLIENT_ID="" \ -e OCEAN__PORT__CLIENT_SECRET="" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ -e OCEAN__INTEGRATION__IDENTIFIER="ocean-custom" \ -e OCEAN__INTEGRATION__TYPE="custom" \ -e OCEAN__EVENT_LISTENER='{"type":"POLLING"}' \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://api.yourcompany.com" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="custom" \ -e OCEAN__INTEGRATION__CONFIG__CUSTOM_AUTH_REQUEST='{"endpoint":"/oauth/token","method":"POST","headers":{"Content-Type":"application/x-www-form-urlencoded"},"bodyForm":"grant_type=client_credentials&client_id=&client_secret=","reauthenticateIntervalSeconds":3600}' \ -e OCEAN__INTEGRATION__CONFIG__CUSTOM_AUTH_REQUEST_TEMPLATE='{"headers":{"Authorization":"Bearer {{.access_token}}"}}' ghcr.io/port-labs/port-ocean-custom:latest ``` ## Common examples[​](#common-examples "Direct link to Common examples") ### OAuth2 client credentials flow[​](#oauth2-client-credentials-flow "Direct link to OAuth2 client credentials flow") Use this pattern for OAuth2 client credentials grants: ``` { "customAuthRequest": { "endpoint": "/oauth/token", "method": "POST", "headers": { "Content-Type": "application/x-www-form-urlencoded" }, "bodyForm": "grant_type=client_credentials&client_id=&client_secret=", "reauthenticateIntervalSeconds": 3600 }, "customAuthRequestTemplate": { "headers": { "Authorization": "Bearer {{.access_token}}" } } } ``` **Expected auth response:** ``` { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600 } ``` ### API key in query parameter[​](#api-key-in-query-parameter "Direct link to API key in query parameter") For APIs that return an API key to use in query parameters: ``` { "customAuthRequest": { "endpoint": "/api/auth/login", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "username": "", "password": "" } }, "customAuthRequestTemplate": { "queryParams": { "api_key": "{{.apiKey}}" } } } ``` **Expected auth response:** ``` { "apiKey": "abc123xyz789", "userId": "12345" } ``` ### JWT token authentication[​](#jwt-token-authentication "Direct link to JWT token authentication") For JWT-based authentication systems: ``` { "customAuthRequest": { "endpoint": "/v1/auth/token", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "clientId": "", "clientSecret": "" } }, "customAuthRequestTemplate": { "headers": { "Authorization": "Bearer {{.jwt}}", "X-API-Version": "{{.version}}" } } } ``` **Expected auth response:** ``` { "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "version": "2.0", "expiresAt": "2024-12-31T23:59:59Z" } ``` ### Nested response structure[​](#nested-response-structure "Direct link to Nested response structure") If your authentication service returns nested data, use dot notation: ``` { "customAuthRequest": { "endpoint": "/auth/login", "method": "POST", "body": { "credentials": { "username": "", "password": "" } } }, "customAuthRequestTemplate": { "headers": { "Authorization": "Bearer {{.data.token}}", "X-Session-ID": "{{.data.sessionId}}" } } } ``` **Expected auth response:** ``` { "status": "success", "data": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "sessionId": "sess_abc123" } } ``` ### Token in multiple places[​](#token-in-multiple-places "Direct link to Token in multiple places") You can inject the same token into headers, query params, and body simultaneously: ``` { "customAuthRequest": { "endpoint": "/v1/auth/token", "method": "POST", "body": { "clientId": "", "clientSecret": "" } }, "customAuthRequestTemplate": { "headers": { "Authorization": "Bearer {{.access_token}}" }, "queryParams": { "token": "{{.access_token}}" }, "body": { "auth_token": "{{.access_token}}" } } } ``` ## Token refresh behavior[​](#token-refresh-behavior "Direct link to Token refresh behavior") The integration handles token refresh in two ways: ### Proactive refresh[​](#proactive-refresh "Direct link to Proactive refresh") If `reauthenticateIntervalSeconds` is configured, tokens refresh automatically 60 seconds before expiration. For example, if you set `reauthenticateIntervalSeconds: 3600`, the integration will refresh the token after 3540 seconds (59 minutes). ### Reactive refresh[​](#reactive-refresh "Direct link to Reactive refresh") If `reauthenticateIntervalSeconds` is not configured, tokens only refresh when the integration receives a 401 (Unauthorized) response. The integration will: 1. Attempt to reauthenticate once 2. Retry the failed request with the new token 3. If reauthentication fails, throw an error visible in the logs ## Error handling[​](#error-handling "Direct link to Error handling") If authentication fails, errors are visible in the integration logs. Common failure scenarios: * **Auth endpoint returns error**: The integration fails to start and logs the error * **Missing token field**: If the expected field (e.g., `{{.access_token}}`) is not in the response, an error is thrown * **Reauthentication fails**: If refresh fails after a 401 error, the integration logs the error and stops processing requests ### Verify configuration[​](#verify-configuration "Direct link to Verify configuration") Ensure your configuration follows these rules: * At least one of `headers`, `queryParams`, or `body` must be provided in `customAuthRequestTemplate` * `body` and `bodyForm` cannot both be specified in `customAuthRequest` * Template paths (e.g., `{{.access_token}}`) must match fields in your auth response * Replace placeholders (e.g., ``, ``) with your actual values ### Test the authentication endpoint[​](#test-the-authentication-endpoint "Direct link to Test the authentication endpoint") Before configuring the integration, test your authentication endpoint directly: ``` curl -X POST https://api.yourcompany.com/oauth/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=YOUR_ID&client_secret=YOUR_SECRET" ``` Verify that the response contains the fields you're trying to extract with template syntax. --- # Setup Using this installation option means that the integration will run in your own infrastructure, giving you full control over resources and configuration. You can deploy it using Helm or Docker, and it will run continuously in your environment to keep your data synchronized with Port. High-scale environments For high-scale environments with large datasets, allocate sufficient CPU and memory based on your data volume. ## Installation[​](#installation "Direct link to Installation") Choose your preferred deployment method: * Helm * Docker To install the integration using Helm: 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) in your portal. 2. Select the **Real-time and always on** method: ![Real-time self-hosted installation method selection](/img/sync-data-to-catalog/selfHostedMethod.png) 3. A `helm` command will be displayed, with default values already filled out (e.g. your Port client ID, client secret, etc). Copy the command, replace the placeholders with your values, then run it in your terminal to install the integration. Alternatively, you can install manually using the following steps: 1. Add Port's Helm chart repository: ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts ``` 2. Install the Helm chart with your configuration: ``` helm install ocean-custom port-labs/port-ocean \ --set port.clientId="" \ --set port.clientSecret="" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set scheduledResyncInterval=60 \ --set integration.identifier="ocean-custom" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://api.yourcompany.com" \ --set integration.config.authType="bearer_token" \ --set integration.secrets.apiToken="" \ --set integration.config.paginationType="page" \ --set integration.config.pageSize=100 ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` To install the integration using Docker: 1. Pull the Docker image: ``` docker pull ghcr.io/port-labs/port-ocean-custom:latest ``` 2. Run the container with your configuration: ``` docker run -i --rm \ -e OCEAN__PORT__CLIENT_ID="" \ -e OCEAN__PORT__CLIENT_SECRET="" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SCHEDULED_RESYNC_INTERVAL=60 \ -e OCEAN__INTEGRATION__IDENTIFIER="ocean-custom" \ -e OCEAN__INTEGRATION__TYPE="custom" \ -e OCEAN__EVENT_LISTENER='{"type":"POLLING"}' \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://api.yourcompany.com" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="page" \ -e OCEAN__INTEGRATION__CONFIG__PAGE_SIZE=100 \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Health check endpoints All Ocean integrations expose liveness and readiness endpoints at `/health/live` and `/health/ready`. The readiness endpoint returns `503` until the integration finishes starting up. For example, if your integration is accessible at `https://your-integration-host:8000`, use `https://your-integration-host:8000/health/ready` for your readiness probe. See [integration endpoints](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md#integration-endpoints) for details. ## Configuration parameters[​](#configuration-parameters "Direct link to Configuration parameters") This table summarizes the available parameters for the installation. | Parameter | Description | Example | Required | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | -------- | | `port.clientId` | Your port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.clientSecret` | Your port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.baseUrl` | Your Port API URL - `https://api.port.io` for EU, `https://api.us.port.io` for US | | ✅ | | `integration.config.baseUrl` | The root URL of your API (e.g., `https://api.yourcompany.com`) | | ✅ | | `integration.config.authType` | Authentication type: `bearer_token`, `api_key`, `basic`, `custom`, or `none`. For custom authentication (OAuth2, JWT, etc.), see [custom authentication](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/custom-authentication/.md) | bearer\_token | ✅ | | `integration.secrets.apiToken` | Bearer token for authentication (required when `authType` is `bearer_token`) | | ❌ | | `integration.config.paginationType` | Pagination type: `offset`, `page`, `cursor`, or `none` | page | ❌ | | `integration.config.pageSize` | Number of items per page (for offset/page pagination) | 100 | ❌ | | `integration.config.timeout` | Request timeout in seconds (default: 30) | 30 | ❌ | | `integration.eventListener.type` | The event listener type. Read more about [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) | POLLING | ✅ | | `integration.type` | The integration to be installed | custom | ✅ | | `scheduledResyncInterval` | The number of minutes between each resync. When not set the integration will resync for each event listener resync event. Read more about [scheduledResyncInterval](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) | 60 | ❌ | | `initializePortResources` | Controls legacy initialization behavior. Do not set this parameter to `false` to prevent default resource creation; use `createPortResourcesOrigin` instead. | true | ❌ | | `createPortResourcesOrigin` | Set to `Empty` to register only the integration without creating default blueprints or mappings. For Docker, set `OCEAN__CREATE_PORT_RESOURCES_ORIGIN="Empty"`. | Empty | ❌ | | `sendRawDataExamples` | Enable sending raw data examples from the third party API to port for testing and managing the integration mapping. Default is true | true | ❌ | Start without default resources For Helm deployments, set `createPortResourcesOrigin: Empty` in your values file or pass `--set createPortResourcesOrigin="Empty"`. The integration registers in Port, but it does not create default blueprints or mappings. ## Recommended resource sizes To ensure optimal performance and avoid out-of-memory (OOM) errors, we recommend the following resources for this integration: * **CPU Limit**: `800m` * **CPU Request**: `400m` * **Memory Limit**: `4Gi` * **Memory Request**: `4Gi` ### Set resource values ``` helm install my-integration port-labs/port-ocean \ # ... other parameters --set resources.limits.cpu=800m \ --set resources.limits.memory=4Gi \ --set resources.requests.cpu=400m \ --set resources.requests.memory=4Gi ``` ## Authentication[​](#authentication "Direct link to Authentication") The integration supports several authentication types: * **Bearer token**: Use `authType: "bearer_token"` with an API token * **API key**: Use `authType: "api_key"` with an API key and optional custom header name * **Basic auth**: Use `authType: "basic"` with username and password * **Custom authentication**: Use `authType: "custom"` for OAuth2, JWT, and other dynamic token-based flows. See [custom authentication](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/custom-authentication/.md) for details. * **None**: Use `authType: "none"` for public APIs without authentication ## Ready to build?[​](#ready-to-build "Direct link to Ready to build?") Head to [build your integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md) for a step-by-step guide with an interactive configuration builder. ## More resources[​](#more-resources "Direct link to More resources") For all configuration options, code examples, and advanced use cases, check out the [No-code Ocean custom integration repository on GitHub](https://github.com/port-labs/ocean/tree/main/integrations/custom). --- # No-code Ocean integration The no-code Ocean integration lets you connect Port to any custom API, internal system, or HTTP service without writing code. Each integration instance connects to one API backend, and you can map multiple endpoints through standard Ocean resource configuration. If your API needs custom auth, complex pagination, live events, or other logic that configuration cannot express, use a [custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/overview/.md) instead. ## Video walkthroughs[​](#video-walkthroughs "Direct link to Video walkthroughs") See the no-code Ocean custom integration in action: **Tracking Cursor adoption using the no-code Ocean custom integration** [YouTube video player](https://www.youtube.com/embed/XvhNsbp4p-U)
**Creating a Pokedex in Port using Pokemon open API** [YouTube video player](https://www.youtube.com/embed/OVVKySiXaOg)
## When to use this integration[​](#when-to-use-this-integration "Direct link to When to use this integration") This integration is ideal when: * No native Port integration exists for your tool or service. * You're working with internal or custom-built APIs. * Your API follows REST conventions (JSON responses, HTTP methods). * You want a configuration-only solution without custom code. ### No-code vs custom-coded[​](#no-code-vs-custom-coded "Direct link to No-code vs custom-coded") | | No-code Ocean custom integration | [Custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/overview/.md) | | -------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | **How you build it** | Configuration only (endpoints, auth, JQ mapping). | Python code with the Ocean framework. | | **Best for** | REST APIs that return JSON over HTTP. | Complex auth, pagination, nested data, or non-standard APIs. | | **Live events** | Currently not supported. | Full control via Ocean event processors. | | **Maintenance** | Port hosts the runtime (or you run the generic custom image). | You own the app, image, and deployment. | Start with the no-code option when it covers your API. Choose custom-coded when you need logic that configuration alone cannot express. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before installing, gather this information about your API: ### 1. Authentication[​](#1-authentication "Direct link to 1. Authentication") How does your API verify requests? * **Bearer Token:** OAuth2 tokens, personal access tokens (most modern APIs). * **API Key:** Custom header like `X-API-Key` or `Authorization`. * **Basic Auth:** Username and password (legacy systems). * **Custom Authentication:** OAuth2 client credentials, JWT, and other dynamic token-based flows (self-hosted only).
See [custom authentication](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/custom-authentication/.md) for details and a full list of [supported authentication methods](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/custom-authentication/.md#supported-authentication-methods). * **None:** Public APIs. **Where to find it:** Check your API's documentation or settings page. Look for sections titled "API Keys," "Access Tokens," or "Authentication." Authentication limitations Some advanced authentication methods like `private_key_jwt` (RFC 7523) and mTLS are not natively supported. See the [authentication capabilities table](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/custom-authentication/.md#supported-authentication-methods) for workarounds. ### 2. Endpoints[​](#2-endpoints "Direct link to 2. Endpoints") Which API endpoint returns the data you want to ingest? **Example:** `/api/v1/users`, `/v2/projects`, `/tickets` **How to find it:** Check your API documentation for available endpoints. Look for GET endpoints that return lists of resources. ### 3. Data structure[​](#3-data-structure "Direct link to 3. Data structure") Where is the actual data in your API's response? **Direct array:** ``` [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"} ] ``` **Nested data:** ``` { "data": [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"} ] } ``` **Deeply nested:** ``` { "response": { "users": { "items": [ {"id": 1, "name": "Alice"} ] } } } ``` You will use a [JQ](https://jqlang.org/manual/) `data_path` expression in your mapping to tell the integration where to find the array of items (e.g., `.data`, `.users.items`). ## Installation methods[​](#installation-methods "Direct link to Installation methods") The no-code Ocean custom integration can be installed in two ways, depending on your requirements and infrastructure preferences: * [Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md) * [Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md) ### Hosted by Port[​](#hosted-by-port "Direct link to Hosted by Port") Port hosts and manages the integration infrastructure for you. This is the simplest option, you configure the connection settings through the Port UI, and Port handles the rest. The integration runs on Port's infrastructure with a customizable resync interval to keep your data up to date. **Best for:** Quick setup, or when you prefer a managed solution. ### Self-hosted[​](#self-hosted "Direct link to Self-hosted") You run the integration in your own infrastructure using Helm or Docker. This gives you full control over resources, configuration, and performance tuning. The integration runs continuously in your environment and can be customized to meet your specific needs. **Best for:** High-scale environments with large datasets that require better control over resources and performance tuning. ## Available integrations[​](#available-integrations "Direct link to Available integrations") The following integrations were built using the no-code Ocean custom integration framework: **AI usage metrics** * [OpenAI](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/openai/.md) **APM & alerting** * [Azure Monitor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/azure-monitor/.md) * [Prometheus](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/prometheus/.md) **Automation platforms** * [n8n](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/n8n/.md) * [Zapier](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/zapier/.md) **Code quality & security** * [BrowserStack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/browserstack/.md) **Identity providers** * [Microsoft Entra ID](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/azure-ad/.md) **Incident management** * [Incident.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/incident-io/.md) * [Jira Service Management](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/jira-service-management/.md) * [Rootly](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/rootly/.md) **Other** * [Slack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/slack/.md) ## Next steps[​](#next-steps "Direct link to Next steps") **[Review the configuration guide](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/configuration/.md)** - Learn about prerequisites, resource mapping, and advanced configuration options that apply to both installation methods. --- # Create a custom integration Port allows you to create custom integrations to ingest data from any tool or platform. You can model the data in Port any way you like, and ingest data using one of the supported methods described in this page. ## Why create a custom integration?[​](#why-create-a-custom-integration "Direct link to Why create a custom integration?") * The tool you want to integrate with is not yet available in our [integrations library](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#available-plug--play-integrations). * You wish to create your own data model and/or ingest data using a different method than the one provided in our integrations. * You wish to integrate Port with an internal tool in your organization. ## How to create a custom integration[​](#how-to-create-a-custom-integration "Direct link to How to create a custom integration") Generally, integrating a platform/tool with Port consists of 3 steps: ![Custom integration process for creating a Port data model, sending data through IaC, webhooks, or the API, and mapping it into blueprints](/img/software-catalog/integration-process.png) ### 1. Define your data model[​](#1-define-your-data-model "Direct link to 1. Define your data model") * Define how your data will be represented in Port, by creating one or more [blueprints](/context-lake/data-model/setup-blueprint/overview/.md) and their properties. * Determine the relationships between your new blueprints and other data models in your catalog, using [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md). ### 2. Ingest data to catalog[​](#2-ingest-data-to-catalog "Direct link to 2. Ingest data to catalog") Use one of the supported methods to ingest data from your tool into Port: * [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) **(recommended!)** - connect a REST API with configuration only. * [Custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/overview/.md) - write Python with the Ocean framework when the no-code option is not enough. * [Webhooks](/context-lake/ingestion/ingest-data-into-port/webhook/ingest-via-webhooks-overview/.md). * [Port API](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md). * [IaC](/context-lake/ingestion/ingest-data-into-port/other/iac/.md). ### 3. Configure the integration[​](#3-configure-the-integration "Direct link to 3. Configure the integration") * When using a webhook, the last step is to [configure the integration's mapping](/context-lake/ingestion/configure-mapping/overview/.md), in order to tell your integration where to find the data in your tool, and how to map it to your blueprint/s. * When using Port's API, there is no need to configure mapping. Since you are directly interacting with the software catalog, the mapping is done as part of the ingestion process. ## Choose an Ocean approach[​](#choose-an-ocean-approach "Direct link to Choose an Ocean approach") Most of Port's integrations were developed using the **Ocean framework**.
Ocean is an open-source framework that simplifies connecting external tools to Port. Use the [no-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) when you can describe the API with endpoints, auth, and JQ mapping. Use a [custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/overview/.md) when you need custom Python logic for auth, pagination, live events, or actions. To contribute a public integration to the community, see the [Ocean repository contributing guide](https://github.com/port-labs/ocean/blob/main/CONTRIBUTING.md). --- # Available integrations Browse all available Port native integrations. Click an integration to open its documentation. Search integrations... All64Git7Kubernetes stack8Argo CD1Cloud providers4Project management3CI/CD2Feature management1APM & alerting6Incident management8Code quality & security8Terraform1Cloud cost2Event processing1AI usage metrics4Automation platforms2Identity providers2Other4 [![GitHub icon](/img/guides/icons/GitHub.svg)![GitHub icon](/img/guides/icons/dark/GitHub.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md) ### [GitHub (Ocean)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md) [![GitHub icon](/img/guides/icons/GitHub.svg)![GitHub icon](/img/guides/icons/dark/GitHub.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) ### [GitHub (sunset)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) [![GitLab icon](/img/guides/icons/GitLab.svg)![GitLab icon](/img/guides/icons/GitLab.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md) ### [GitLab v2](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md) [![GitLab icon](/img/guides/icons/GitLab.svg)![GitLab icon](/img/guides/icons/GitLab.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md) ### [GitLab (deprecated)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md) [![BitBucket icon](/img/guides/icons/BitBucket.svg)![BitBucket icon](/img/guides/icons/BitBucket.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/.md) ### [BitBucket](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/.md) [![BitBucket icon](/img/guides/icons/BitBucket.svg)![BitBucket icon](/img/guides/icons/BitBucket.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/.md) ### [BitBucket server](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/.md) [![AzureDevops icon](/img/guides/icons/AzureDevops.svg)![AzureDevops icon](/img/guides/icons/AzureDevops.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md) ### [AzureDevops](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md) [![Jenkins icon](/img/guides/icons/Jenkins.svg)![Jenkins icon](/img/guides/icons/Jenkins.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/jenkins/.md) ### [Jenkins](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/jenkins/.md) [![Octopus Deploy icon](/img/guides/icons/OctopusDeploy.svg)![Octopus Deploy icon](/img/guides/icons/OctopusDeploy.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/.md) ### [Octopus Deploy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/.md) [![ArgoCD icon](/img/guides/icons/ArgoCD.svg)![ArgoCD icon](/img/guides/icons/ArgoCD.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd/.md) ### [ArgoCD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd/.md) [![Komodor icon](/img/guides/icons/Komodor.svg)![Komodor icon](/img/guides/icons/Komodor.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/komodor/.md) ### [Komodor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/komodor/.md) [![Kubernetes icon](/img/guides/icons/Kubernetes.svg)![Kubernetes icon](/img/guides/icons/Kubernetes.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ### [Kubernetes](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) [![Istio icon](/img/guides/icons/Istio.svg)![Istio icon](/img/guides/icons/Istio.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/istio/.md) ### [Istio](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/istio/.md) [![FluxCD icon](/img/guides/icons/FluxCD.svg)![FluxCD icon](/img/guides/icons/FluxCD.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/fluxcd/.md) ### [FluxCD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/fluxcd/.md) [![Knative icon](/img/guides/icons/Knative.svg)![Knative icon](/img/guides/icons/Knative.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/knative/.md) ### [Knative](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/knative/.md) [![Kyverno icon](/img/guides/icons/Kyverno.svg)![Kyverno icon](/img/guides/icons/Kyverno.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/kyverno/.md) ### [Kyverno](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/kyverno/.md) [![OpenShift icon](/img/guides/icons/OpenShift.svg)![OpenShift icon](/img/guides/icons/OpenShift.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/openshift/.md) ### [OpenShift](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/openshift/.md) [![Trivy icon](/img/guides/icons/Trivy.svg)![Trivy icon](/img/guides/icons/Trivy.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/trivy/.md) ### [Trivy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/trivy/.md) [![Jira icon](/img/guides/icons/Jira.svg)![Jira icon](/img/guides/icons/Jira.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/.md) ### [Jira](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/.md) [![Jira icon](/img/guides/icons/Jira.svg)![Jira icon](/img/guides/icons/Jira.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/.md) ### [Jira server (self-hosted)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/.md) [![Linear icon](/img/guides/icons/Linear.svg)![Linear icon](/img/guides/icons/dark/Linear.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/linear/.md) ### [Linear](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/linear/.md) [![AWS icon](/img/guides/icons/AWS.svg)![AWS icon](/img/guides/icons/dark/AWS.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) ### [AWS Legacy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) [![AWS icon](/img/guides/icons/AWS.svg)![AWS icon](/img/guides/icons/dark/AWS.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/Overview/.md) ### [AWS](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/Overview/.md) [![Azure icon](/img/guides/icons/Azure.svg)![Azure icon](/img/guides/icons/Azure.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/.md) ### [Azure](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/.md) [![GCP icon](/img/guides/icons/GCP.svg)![GCP icon](/img/guides/icons/GCP.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/.md) ### [GCP](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/.md) [![Terraform icon](/img/guides/icons/Terraform.svg)![Terraform icon](/img/guides/icons/Terraform.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/terraform-cloud/.md) ### [Terraform](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/terraform-cloud/.md) [![Datadog icon](/img/guides/icons/Datadog.svg)![Datadog icon](/img/guides/icons/dark/Datadog.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/.md) ### [Datadog](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/.md) [![New Relic icon](/img/guides/icons/NewRelic.svg)![New Relic icon](/img/guides/icons/NewRelic.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/newrelic/.md) ### [New Relic](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/newrelic/.md) [![Sentry icon](/img/guides/icons/Sentry.svg)![Sentry icon](/img/guides/icons/dark/Sentry.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/sentry/.md) ### [Sentry](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/sentry/.md) [![Prometheus icon](/img/guides/icons/Prometheus.svg)![Prometheus icon](/img/guides/icons/Prometheus.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/prometheus/.md) ### [Prometheus](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/prometheus/.md) [![Dynatrace icon](/img/guides/icons/Dynatrace.svg)![Dynatrace icon](/img/guides/icons/Dynatrace.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/dynatrace/.md) ### [Dynatrace](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/dynatrace/.md) [![Azure icon](/img/guides/icons/Azure.svg)![Azure icon](/img/guides/icons/Azure.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/azure-monitor/.md) ### [Azure Monitor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/azure-monitor/.md) [![Snyk icon](/img/guides/icons/Snyk.svg)![Snyk icon](/img/guides/icons/Snyk.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/snyk/.md) ### [Snyk](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/snyk/.md) [![Wiz icon](/img/guides/icons/Wiz.svg)![Wiz icon](/img/guides/icons/Wiz.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/.md) ### [Wiz](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/.md) [![Mend icon](/img/guides/icons/Mend.svg)![Mend icon](/img/guides/icons/Mend.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/mend/.md) ### [Mend.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/mend/.md) [![SonarQube icon](/img/guides/icons/SonarQube.svg)![SonarQube icon](/img/guides/icons/SonarQube.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/.md) ### [SonarQube](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/.md) [![Aikido icon](/img/guides/icons/Aikido.svg)![Aikido icon](/img/guides/icons/Aikido.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/aikido/.md) ### [Aikido](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/aikido/.md) [![Checkmarx icon](/img/guides/icons/Checkmarx.svg)![Checkmarx icon](/img/guides/icons/Checkmarx.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/.md) ### [Checkmarx](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/.md) [![Armorcode icon](/img/guides/icons/Armorcode.svg)![Armorcode icon](/img/guides/icons/Armorcode.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/armorcode/.md) ### [Armorcode](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/armorcode/.md) [![BrowserStack icon](/img/guides/icons/BrowserStack.svg)![BrowserStack icon](/img/guides/icons/BrowserStack.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/browserstack/.md) ### [BrowserStack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/browserstack/.md) [![Launchdarkly icon](/img/guides/icons/Launchdarkly.svg)![Launchdarkly icon](/img/guides/icons/dark/Launchdarkly.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/feature-management/launchdarkly/.md) ### [Launchdarkly](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/feature-management/launchdarkly/.md) [![PagerDuty icon](/img/guides/icons/PagerDuty.svg)![PagerDuty icon](/img/guides/icons/PagerDuty.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/.md) ### [PagerDuty](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/.md) [![StatusPage icon](/img/guides/icons/StatusPage.svg)![StatusPage icon](/img/guides/icons/StatusPage.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/.md) ### [StatusPage](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/.md) [![FireHydrant icon](/img/guides/icons/FireHydrant.svg)![FireHydrant icon](/img/guides/icons/FireHydrant.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/firehydrant/.md) ### [FireHydrant](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/firehydrant/.md) [![OpsGenie icon](/img/guides/icons/OpsGenie.svg)![OpsGenie icon](/img/guides/icons/OpsGenie.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/.md) ### [OpsGenie](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/.md) [![ServiceNow icon](/img/guides/icons/ServiceNow.svg)![ServiceNow icon](/img/guides/icons/ServiceNow.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/servicenow/.md) ### [ServiceNow](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/servicenow/.md) [![Rootly icon](/img/guides/icons/Rootly.svg)![Rootly icon](/img/guides/icons/Rootly.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/rootly/.md) ### [Rootly](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/rootly/.md) [![IncidentIO icon](/img/guides/icons/IncidentIO.svg)![IncidentIO icon](/img/guides/icons/IncidentIO.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/incident-io/.md) ### [Incident.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/incident-io/.md) [![Jira icon](/img/guides/icons/Jira.svg)![Jira icon](/img/guides/icons/Jira.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/jira-service-management/.md) ### [Jira Service Management](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/jira-service-management/.md) [![Kubecost icon](/img/guides/icons/Kubecost.svg)![Kubecost icon](/img/guides/icons/Kubecost.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/.md) ### [Kubecost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/.md) [![Opencost icon](/img/guides/icons/Opencost.svg)![Opencost icon](/img/guides/icons/Opencost.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/opencost/.md) ### [Opencost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/opencost/.md) [![Kafka icon](/img/guides/icons/Kafka.svg)![Kafka icon](/img/guides/icons/dark/Kafka.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/event-processing/kafka/.md) ### [Kafka](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/event-processing/kafka/.md) [![Okta icon](/img/guides/icons/Okta.svg)![Okta icon](/img/guides/icons/dark/Okta.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/okta/.md) ### [Okta](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/okta/.md) [![Azure icon](/img/guides/icons/Azure.svg)![Azure icon](/img/guides/icons/Azure.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/azure-ad/.md) ### [Microsoft Entra ID](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/azure-ad/.md) [![Amplication icon](/img/guides/icons/Amplication.svg)![Amplication icon](/img/guides/icons/Amplication.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/amplication/.md) ### [Amplication](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/amplication/.md) [![Backstage icon](/img/guides/icons/Backstage.svg)![Backstage icon](/img/guides/icons/dark/Backstage.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/backstage/.md) ### [Backstage](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/backstage/.md) [![Slack icon](/img/guides/icons/Slack.svg)![Slack icon](/img/guides/icons/Slack.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/slack/.md) ### [Slack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/slack/.md) [![Zapier icon](/img/guides/icons/Zapier.svg)![Zapier icon](/img/guides/icons/Zapier.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/zapier/.md) ### [Zapier](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/zapier/.md) [![n8n icon](/img/guides/icons/n8n.svg)![n8n icon](/img/guides/icons/n8n.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/n8n/.md) ### [n8n](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/n8n/.md) [![APIcatalog icon](/img/guides/icons/APIcatalog.svg)![APIcatalog icon](/img/guides/icons/APIcatalog.svg)](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) ### [Ocean custom](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) [![Github Copilot icon](/img/guides/icons/GithubCopilot.svg)![Github Copilot icon](/img/guides/icons/dark/GithubCopilot.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/github-copilot/.md) ### [Github Copilot](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/github-copilot/.md) [![Claude AI icon](/img/guides/icons/ClaudeAI.svg)![Claude AI icon](/img/guides/icons/ClaudeAI.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/claude/.md) ### [Claude AI](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/claude/.md) [![Cursor icon](/img/guides/icons/Cursor.svg)![Cursor icon](/img/guides/icons/dark/Cursor.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/cursor/.md) ### [Cursor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/cursor/.md) [![OpenAI icon](/img/guides/icons/OpenAI.svg)![OpenAI icon](/img/guides/icons/dark/OpenAI.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/openai/.md) ### [OpenAI](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/openai/.md) --- # Claude AI metrics ![ClaudeAI icon](/img/guides/icons/ClaudeAI.svg)![ClaudeAI icon](/img/guides/icons/ClaudeAI.svg) Loading version... Port's Claude integration allows you to ingest Claude AI (Enterprise) and Claude Platform usage, cost, skill adoption, and Claude Code analytics metrics into your software catalog. Supported resources ![External link icon](/img/icons/external-link.svg)[claude-ai-user-activity](https://support.claude.com/en/articles/13703965-claude-enterprise-analytics-api-reference-guide)[claude-ai-user-usage](https://support.claude.com/en/articles/13703965-claude-enterprise-analytics-api-reference-guide)[claude-ai-user-cost](https://support.claude.com/en/articles/13703965-claude-enterprise-analytics-api-reference-guide)[claude-ai-skill-usage](https://platform.claude.com/docs/en/api/admin/analytics/skills/list)[claude-platform-usage-record](https://platform.claude.com/docs/en/api/admin/usage_report/retrieve_messages)[claude-usage-record](https://platform.claude.com/docs/en/api/admin/usage_report/retrieve_messages)[claude-platform-cost-record](https://platform.claude.com/docs/en/api/admin/cost_report/retrieve)[claude-cost-record](https://platform.claude.com/docs/en/api/admin/cost_report/retrieve)[claude-platform-code-analytics](https://platform.claude.com/docs/en/api/admin/usage_report/retrieve_claude_code)[claude-code-analytics](https://platform.claude.com/docs/en/api/admin/usage_report/retrieve_claude_code) ## Deployment modes[​](#deployment-modes "Direct link to Deployment modes") The integration runs in one of two deployment modes, controlled by the `isClaudeEnterprise` configuration option. Each mode targets a different set of Claude API endpoints and requires an API key with a different scope. | Mode | `isClaudeEnterprise` | Resources synced | Required API key scope | | ---------------------- | -------------------- | ------------------------------------------------------------------------------------------------- | ---------------------- | | Claude AI (Enterprise) | `true` (default) | `claude-ai-user-activity`, `claude-ai-user-usage`, `claude-ai-user-cost`, `claude-ai-skill-usage` | `read:analytics` | | Claude Platform | `false` | `claude-platform-usage-record`, `claude-platform-cost-record`, `claude-platform-code-analytics` | `api:admin` | Use **Claude AI (Enterprise)** mode to track per-user engagement, token usage, cost, and skill adoption for everyday Claude usage such as the web app and Claude Code. Use **Claude Platform** mode to track aggregated usage and cost for Claude Platform API consumption, typically used by developers. Blueprints for both modes When `initializePortResources` is enabled, the integration creates the default blueprints for **both** Claude AI and Claude Platform, regardless of the selected mode. Only the resources for the active mode are populated with data. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * An Anthropic API key for the deployment mode you want to use: * **Claude AI (Enterprise)** mode requires an analytics API key with the `read:analytics` scope, minted by a Primary Owner from `claude.ai/analytics/api-keys`. * **Claude Platform** mode requires an [Admin API key](https://platform.claude.com/docs/en/manage-claude/admin-api-keys) with the `api:admin` scope and access to the usage and cost reports. * Your Port user role is set to `Admin`. ## Setup[​](#setup "Direct link to Setup") Choose one of the installation methods below. If you are not sure which option to use, review the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party API into Port. The mapping uses the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to transform fields and shape incoming Claude data into Port entities. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration. It maps the Claude AI (Enterprise) resources, which are synced in the default deployment mode: **Default mapping configuration (click to expand)** ``` resources: - kind: claude-ai-user-activity selector: query: 'true' timeFrame: 30 port: entity: mappings: identifier: .user.id + "-" + .__date title: (.user.email_address // .user.id) + " - " + .__date blueprint: '"claude_ai_user_activity"' properties: record_date: .__date + "T00:00:00Z" user_id: .user.id email: .user.email_address chat_conversation_count: (.chat_metrics.distinct_conversation_count // 0) chat_message_count: (.chat_metrics.message_count // 0) chat_thinking_message_count: (.chat_metrics.thinking_message_count // 0) chat_skills_used_count: (.chat_metrics.distinct_skills_used_count // 0) chat_connectors_used_count: (.chat_metrics.connectors_used_count // 0) code_session_count: (.claude_code_metrics.core_metrics.distinct_session_count // 0) code_commit_count: (.claude_code_metrics.core_metrics.commit_count // 0) code_pull_request_count: (.claude_code_metrics.core_metrics.pull_request_count // 0) lines_added: (.claude_code_metrics.core_metrics.lines_of_code.added_count // 0) lines_removed: (.claude_code_metrics.core_metrics.lines_of_code.removed_count // 0) edit_tool_accepted: (.claude_code_metrics.tool_actions.edit_tool.accepted_count // 0) edit_tool_rejected: (.claude_code_metrics.tool_actions.edit_tool.rejected_count // 0) write_tool_accepted: (.claude_code_metrics.tool_actions.write_tool.accepted_count // 0) write_tool_rejected: (.claude_code_metrics.tool_actions.write_tool.rejected_count // 0) multi_edit_tool_accepted: (.claude_code_metrics.tool_actions.multi_edit_tool.accepted_count // 0) multi_edit_tool_rejected: (.claude_code_metrics.tool_actions.multi_edit_tool.rejected_count // 0) notebook_edit_tool_accepted: (.claude_code_metrics.tool_actions.notebook_edit_tool.accepted_count // 0) notebook_edit_tool_rejected: (.claude_code_metrics.tool_actions.notebook_edit_tool.rejected_count // 0) web_search_count: (.web_search_count // 0) chat_projects_used_count: (.chat_metrics.distinct_projects_used_count // 0) chat_projects_created_count: (.chat_metrics.distinct_projects_created_count // 0) chat_artifacts_created_count: (.chat_metrics.distinct_artifacts_created_count // 0) chat_files_uploaded_count: (.chat_metrics.distinct_files_uploaded_count // 0) chat_shared_conversations_viewed_count: (.chat_metrics.shared_conversations_viewed_count // 0) office_session_count: >- ([.office_metrics.excel.distinct_session_count, .office_metrics.powerpoint.distinct_session_count, .office_metrics.word.distinct_session_count, .office_metrics.outlook.distinct_session_count] | map(. // 0) | add) office_message_count: >- ([.office_metrics.excel.message_count, .office_metrics.powerpoint.message_count, .office_metrics.word.message_count, .office_metrics.outlook.message_count] | map(. // 0) | add) cowork_session_count: (.cowork_metrics.distinct_session_count // 0) cowork_message_count: (.cowork_metrics.message_count // 0) design_session_count: (.design_metrics.distinct_session_count // 0) design_message_count: (.design_metrics.message_count // 0) - kind: claude-ai-user-usage selector: query: 'true' startingAt: '2026-01-01T00:00:00Z' port: entity: mappings: identifier: >- .actor.user_id + "-" + .__starting_at[:10] + "-" + (.product // "all") + "-" + (.model // "all") title: (.actor.name // .actor.email // .actor.user_id) + " - " + .__starting_at[:10] blueprint: '"claude_ai_user_usage"' properties: record_date: .__starting_at range_end: .__ending_at user_id: .actor.user_id user_name: .actor.name email: .actor.email deleted: (.actor.deleted // false) product: .product model: .model context_window: .context_window inference_geo: .inference_geo speed: .speed uncached_input_tokens: (.uncached_input_tokens // 0) cache_creation_1h_tokens: (.cache_creation.ephemeral_1h_input_tokens // 0) cache_creation_5m_tokens: (.cache_creation.ephemeral_5m_input_tokens // 0) cache_read_input_tokens: (.cache_read_input_tokens // 0) output_tokens: (.output_tokens // 0) total_tokens: (.total_tokens // 0) web_search_requests: (.server_tool_use.web_search_requests // 0) requests: (.requests // 0) - kind: claude-ai-user-cost selector: query: 'true' startingAt: '2026-01-01T00:00:00Z' port: entity: mappings: identifier: >- .actor.user_id + "-" + .__starting_at[:10] + "-" + (.product // "all") + "-" + (.model // "all") title: (.actor.name // .actor.email // .actor.user_id) + " - " + .__starting_at[:10] blueprint: '"claude_ai_user_cost"' properties: record_date: .__starting_at range_end: .__ending_at user_id: .actor.user_id user_name: .actor.name email: .actor.email deleted: (.actor.deleted // false) product: .product model: .model currency: (.currency // "USD") amount: ((.amount // "0") | tonumber) / 100 list_amount: ((.list_amount // "0") | tonumber) / 100 requests: (.requests // 0) - kind: claude-ai-skill-usage selector: query: 'true' timeFrame: 30 port: entity: mappings: identifier: >- ((.skill_name // "unknown") + "-" + .__date | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_")) title: ((.skill_display_name // .skill_name // "unknown") + " - " + .__date) blueprint: '"claude_ai_skill_usage"' properties: record_date: .__date + "T00:00:00Z" skill_name: (.skill_name // "unknown") skill_display_name: .skill_display_name distinct_user_count: .distinct_user_count enable_count: .enable_count invocation_count: .invocation_count share_status: .share_status currency: (.currency // "USD") estimated_overage_spend: (if .estimated_overage_spend then (.estimated_overage_spend | tonumber) / 100 else null end) attributed_list_price: (if .attributed_list_price then (.attributed_list_price | tonumber) / 100 else null end) chat_conversation_skill_used_count: .chat_metrics.distinct_conversation_skill_used_count code_session_skill_used_count: .claude_code_metrics.distinct_session_skill_used_count cowork_session_skill_used_count: .cowork_metrics.distinct_session_skill_used_count excel_session_skill_used_count: .office_metrics.excel.distinct_session_skill_used_count powerpoint_session_skill_used_count: .office_metrics.powerpoint.distinct_session_skill_used_count word_session_skill_used_count: .office_metrics.word.distinct_session_skill_used_count outlook_session_skill_used_count: .office_metrics.outlook.distinct_session_skill_used_count ``` ### Advanced selector configuration[​](#advanced-selector-configuration "Direct link to Advanced selector configuration") The available selectors depend on the active deployment mode. **`query`** applies to all resource kinds in both modes and defaults to `'true'`. * Claude AI (Enterprise) * Claude Platform #### `claude-ai-user-activity` This resource returns data for one specific day per call. The endpoint only returns data that is at least three days old, so the date window always ends roughly three days ago. To control the start of the window, provide one of `timeFrame` or `startingDate` (mutually exclusive). When you set neither, the integration defaults to a 30-day lookback. | Field | Format | Description | | -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `timeFrame` | `number` | Number of days to look back, ending around three days ago. Must be greater than `0`. Mutually exclusive with `startingDate`. Defaults to `30` when neither field is set. | | `startingDate` | `YYYY-MM-DD` | Iterates from this date once per day up to around three days ago, clamped to `2026-01-01` (the earliest available data). Use for historical backfills. Mutually exclusive with `timeFrame`. | Using `timeFrame` (recommended for ongoing syncs): ``` selector: query: 'true' timeFrame: 30 # look back 30 days, one API call per day ``` Using `startingDate` (for a historical backfill): ``` selector: query: 'true' startingDate: '2026-01-01' # iterate from this date, one API call per day ``` #### `claude-ai-skill-usage` This resource returns org-level [skill usage](https://platform.claude.com/docs/en/api/admin/analytics/skills/list) for one specific day per call. The endpoint only returns data once it is available, typically with a 1-2 day lag, and is no earlier than `2026-01-01`. To control the start of the window, provide one of `timeFrame` or `startingDate` (mutually exclusive). When you set neither, the integration defaults to a 30-day lookback. | Field | Format | Description | | -------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `timeFrame` | `number` | Number of days to look back, ending around two days ago. Must be greater than `0`. Mutually exclusive with `startingDate`. Defaults to `30` when neither field is set. | | `startingDate` | `YYYY-MM-DD` | Iterates from this date once per day up to around two days ago, clamped to `2026-01-01` (the earliest available data). Use for historical backfills. Mutually exclusive with `timeFrame`. | | `groupBy` | `array` | Optional. Break skill usage out by one dimension at a time. Allowed values: `user_id`, `product`, `rbac_group_id`. Leave empty for org-level totals. `product` is one of `chat`, `claude_code`, `cowork`, or `office_agent`. See [Breaking down analytics on user and team level](#breaking-down-analytics-on-user-and-team-level). | Using `timeFrame` (recommended for ongoing syncs): ``` selector: query: 'true' timeFrame: 30 # look back 30 days, one API call per day ``` Using `startingDate` (for a historical backfill): ``` selector: query: 'true' startingDate: '2026-01-01' # iterate from this date, one API call per day ``` #### `claude-ai-user-usage` and `claude-ai-user-cost` These resources return per-user data across a date range and share the same selector fields. | Field | Type | Description | | --------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `startingAt` | `string` | RFC 3339 UTC start of the range (inclusive), e.g. `2026-01-01T00:00:00Z`. Automatically clamped to the last 31 days and no earlier than `2026-01-01`. Defaults to `2026-01-01T00:00:00Z`. | | `endingAt` | `string` | RFC 3339 UTC end of the range (exclusive). Defaults to now. The range spans at most 31 days. | | `excludeDeletedUsers` | `boolean` | When `true`, rows for deleted users are omitted. Defaults to `false`. | | `products` | `array` | Filter to specific seat-based product surfaces. Allowed values: `chat`, `claude_code`, `cowork`, `office_agent`, `claude_in_chrome`, `claude_design`. | | `models` | `array` | Filter to specific model names. | | `groupBy` | `array` | Break each user's row out by the given dimensions. Allowed values: `product`, `model`, `context_window`, `inference_geo`, `speed`. | | `contextWindows` | `array` | Filter to specific context-window pricing tiers. Allowed values: `0-200k`, `200k-1M`. | | `inferenceGeos` | `array` | Filter to specific inference regions. Allowed values: `global`, `us`, `not_available`. | | `speeds` | `array` | Filter to fast or standard inference mode. Allowed values: `fast`, `standard`. | ``` selector: query: 'true' startingAt: '2026-01-01T00:00:00Z' endingAt: '2026-01-31T00:00:00Z' excludeDeletedUsers: false products: [chat, claude_code] groupBy: [product, model] ``` The Claude Platform selectors behave as follows: * **`startingDate`** is required for `claude-platform-usage-record` and `claude-platform-cost-record`. For `claude-platform-code-analytics`, either `startingDate` or `timeFrame` must be provided (mutually exclusive). * **`bucketWidth`** controls time granularity for usage and cost resources. * **`groupBy`** is available for `claude-platform-usage-record` and lets you break down usage by dimensions. #### `claude-platform-usage-record` ``` selector: query: 'true' startingDate: '2026-01-01T00:00:00Z' bucketWidth: 1d # supported: 1m | 1h | 1d groupBy: [workspace_id] ``` * `startingDate` must use ISO-8601 UTC format (`YYYY-MM-DDTHH:MM:SSZ`). * `bucketWidth` supports `1m`, `1h`, and `1d` for usage records. * `groupBy` supports dimensions such as `api_key_id`, `workspace_id`, `context_window`, `speed`, `inference_geo`, `account_id`, `service_account_id`, `model`, and `service_tier`. #### `claude-platform-cost-record` ``` selector: query: 'true' startingDate: '2026-01-01T00:00:00Z' bucketWidth: 1d ``` * `startingDate` must use ISO-8601 UTC format (`YYYY-MM-DDTHH:MM:SSZ`). * `bucketWidth` supports `1d` only for cost records in this integration. #### `claude-platform-code-analytics` The API returns data for one specific day per call. To control the date window, provide exactly one of the following fields: | Field | Format | Description | | -------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `timeFrame` | `number` | Number of days to look back from today. The integration calls the API once per day for each of the last N days. Recommended for ongoing syncs. | | `startingDate` | `YYYY-MM-DD` | Iterates from this date to today, calling the API once per day. Use for historical backfills. A warning is logged and no data is fetched if the date is in the future. | Mutually exclusive `timeFrame` and `startingDate` cannot both be set on `claude-platform-code-analytics`. Providing neither or both will cause a validation error. Using `timeFrame` (recommended for ongoing syncs): ``` selector: query: 'true' timeFrame: 30 # fetch the last 30 days, one API call per day ``` Using `startingDate` (for a historical backfill): ``` selector: query: 'true' startingDate: '2026-01-01' # iterate from this date to today ``` ## Mapping & examples per resource[​](#mapping--examples-per-resource "Direct link to Mapping & examples per resource") To view and test the integration mapping against sample API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). * Claude AI (Enterprise) * Claude Platform claude-ai-user-activityclaude-ai-user-usageclaude-ai-user-costclaude-ai-skill-usage BlueprintJSON Create in Port ``` { "identifier": "claude_ai_user_activity", "description": "Per-user Claude (chat) and Claude Code engagement metrics for a single day", "title": "Claude AI User Activity", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Record Date (UTC)" }, "user_id": { "type": "string", "title": "User ID" }, "email": { "type": "string", "format": "user", "title": "Email" }, "chat_conversation_count": { "type": "number", "title": "Chat Conversations" }, "chat_message_count": { "type": "number", "title": "Chat Messages" }, "chat_thinking_message_count": { "type": "number", "title": "Chat Thinking Messages" }, "chat_skills_used_count": { "type": "number", "title": "Chat Skills Used" }, "chat_connectors_used_count": { "type": "number", "title": "Chat Connectors Used" }, "code_session_count": { "type": "number", "title": "Claude Code Sessions" }, "code_commit_count": { "type": "number", "title": "Claude Code Commits" }, "code_pull_request_count": { "type": "number", "title": "Claude Code Pull Requests" }, "lines_added": { "type": "number", "title": "Lines Added" }, "lines_removed": { "type": "number", "title": "Lines Removed" }, "edit_tool_accepted": { "type": "number", "title": "Edit Tool Accepted" }, "edit_tool_rejected": { "type": "number", "title": "Edit Tool Rejected" }, "write_tool_accepted": { "type": "number", "title": "Write Tool Accepted" }, "write_tool_rejected": { "type": "number", "title": "Write Tool Rejected" }, "web_search_count": { "type": "number", "title": "Web Search Count" }, "chat_projects_used_count": { "type": "number", "title": "Chat Projects Used" }, "chat_projects_created_count": { "type": "number", "title": "Chat Projects Created" }, "chat_artifacts_created_count": { "type": "number", "title": "Chat Artifacts Created" }, "chat_files_uploaded_count": { "type": "number", "title": "Chat Files Uploaded" }, "chat_shared_conversations_viewed_count": { "type": "number", "title": "Chat Shared Conversations Viewed" }, "multi_edit_tool_accepted": { "type": "number", "title": "Multi-Edit Tool Accepted" }, "multi_edit_tool_rejected": { "type": "number", "title": "Multi-Edit Tool Rejected" }, "notebook_edit_tool_accepted": { "type": "number", "title": "Notebook Edit Tool Accepted" }, "notebook_edit_tool_rejected": { "type": "number", "title": "Notebook Edit Tool Rejected" }, "office_session_count": { "type": "number", "title": "Office Sessions (Excel/PPT/Word/Outlook)" }, "office_message_count": { "type": "number", "title": "Office Messages" }, "cowork_session_count": { "type": "number", "title": "Cowork Sessions" }, "cowork_message_count": { "type": "number", "title": "Cowork Messages" }, "design_session_count": { "type": "number", "title": "Design Sessions" }, "design_message_count": { "type": "number", "title": "Design Messages" } }, "required": [ "record_date" ] }, "mirrorProperties": {}, "calculationProperties": { "total_lines_changed": { "title": "Total Lines Changed", "calculation": ".properties.lines_added + .properties.lines_removed", "type": "number" }, "edit_acceptance_rate": { "title": "Edit Acceptance Rate", "calculation": "if (.properties.edit_tool_accepted + .properties.edit_tool_rejected) > 0 then (.properties.edit_tool_accepted / (.properties.edit_tool_accepted + .properties.edit_tool_rejected)) * 100 else 0 end", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-ai-user-activity selector: query: 'true' timeFrame: 30 port: entity: mappings: identifier: .user.id + "-" + .__date title: (.user.email_address // .user.id) + " - " + .__date blueprint: '"claude_ai_user_activity"' properties: record_date: .__date + "T00:00:00Z" user_id: .user.id email: .user.email_address chat_conversation_count: (.chat_metrics.distinct_conversation_count // 0) chat_message_count: (.chat_metrics.message_count // 0) chat_thinking_message_count: (.chat_metrics.thinking_message_count // 0) chat_skills_used_count: (.chat_metrics.distinct_skills_used_count // 0) chat_connectors_used_count: (.chat_metrics.connectors_used_count // 0) code_session_count: (.claude_code_metrics.core_metrics.distinct_session_count // 0) code_commit_count: (.claude_code_metrics.core_metrics.commit_count // 0) code_pull_request_count: (.claude_code_metrics.core_metrics.pull_request_count // 0) lines_added: (.claude_code_metrics.core_metrics.lines_of_code.added_count // 0) lines_removed: (.claude_code_metrics.core_metrics.lines_of_code.removed_count // 0) edit_tool_accepted: (.claude_code_metrics.tool_actions.edit_tool.accepted_count // 0) edit_tool_rejected: (.claude_code_metrics.tool_actions.edit_tool.rejected_count // 0) write_tool_accepted: (.claude_code_metrics.tool_actions.write_tool.accepted_count // 0) write_tool_rejected: (.claude_code_metrics.tool_actions.write_tool.rejected_count // 0) multi_edit_tool_accepted: (.claude_code_metrics.tool_actions.multi_edit_tool.accepted_count // 0) multi_edit_tool_rejected: (.claude_code_metrics.tool_actions.multi_edit_tool.rejected_count // 0) notebook_edit_tool_accepted: (.claude_code_metrics.tool_actions.notebook_edit_tool.accepted_count // 0) notebook_edit_tool_rejected: (.claude_code_metrics.tool_actions.notebook_edit_tool.rejected_count // 0) web_search_count: (.web_search_count // 0) chat_projects_used_count: (.chat_metrics.distinct_projects_used_count // 0) chat_projects_created_count: (.chat_metrics.distinct_projects_created_count // 0) chat_artifacts_created_count: (.chat_metrics.distinct_artifacts_created_count // 0) chat_files_uploaded_count: (.chat_metrics.distinct_files_uploaded_count // 0) chat_shared_conversations_viewed_count: (.chat_metrics.shared_conversations_viewed_count // 0) office_session_count: >- ([.office_metrics.excel.distinct_session_count, .office_metrics.powerpoint.distinct_session_count, .office_metrics.word.distinct_session_count, .office_metrics.outlook.distinct_session_count] | map(. // 0) | add) office_message_count: >- ([.office_metrics.excel.message_count, .office_metrics.powerpoint.message_count, .office_metrics.word.message_count, .office_metrics.outlook.message_count] | map(. // 0) | add) cowork_session_count: (.cowork_metrics.distinct_session_count // 0) cowork_message_count: (.cowork_metrics.message_count // 0) design_session_count: (.design_metrics.distinct_session_count // 0) design_message_count: (.design_metrics.message_count // 0) ``` Sample payloadJSON ``` { "user": { "id": "user_01VxtsnMPZAsxdeoXJB9BFp1", "email_address": "dev@domain.io" }, "distinct_user_count": null, "chat_metrics": { "distinct_conversation_count": 2, "message_count": 4, "thinking_message_count": 0, "distinct_projects_used_count": 0, "distinct_projects_created_count": 0, "distinct_artifacts_created_count": 0, "distinct_skills_used_count": 0, "connectors_used_count": 0, "distinct_files_uploaded_count": 0, "shared_conversations_viewed_count": 0, "distinct_shared_artifacts_viewed_count": 0 }, "claude_code_metrics": { "core_metrics": { "distinct_session_count": 0, "commit_count": 0, "pull_request_count": 0, "lines_of_code": { "added_count": 0, "removed_count": 0 } }, "tool_actions": { "edit_tool": { "accepted_count": 0, "rejected_count": 0 }, "multi_edit_tool": { "accepted_count": 0, "rejected_count": 0 }, "write_tool": { "accepted_count": 0, "rejected_count": 0 }, "notebook_edit_tool": { "accepted_count": 0, "rejected_count": 0 } } }, "office_metrics": { "excel": { "distinct_session_count": 0, "message_count": 0, "skills_used_count": 0, "distinct_skills_used_count": 0, "connectors_used_count": 0, "distinct_connectors_used_count": 0 }, "powerpoint": { "distinct_session_count": 0, "message_count": 0, "skills_used_count": 0, "distinct_skills_used_count": 0, "connectors_used_count": 0, "distinct_connectors_used_count": 0 }, "word": { "distinct_session_count": 0, "message_count": 0, "skills_used_count": 0, "distinct_skills_used_count": 0, "connectors_used_count": 0, "distinct_connectors_used_count": 0 }, "outlook": { "distinct_session_count": 0, "message_count": 0, "skills_used_count": 0, "distinct_skills_used_count": 0, "connectors_used_count": 0, "distinct_connectors_used_count": 0 } }, "cowork_metrics": { "distinct_session_count": 0, "action_count": 0, "file_edit_count": null, "edit_tool_count": null, "multi_edit_tool_count": null, "write_tool_count": null, "notebook_edit_tool_count": null, "sessions_with_file_edits_count": null, "dispatch_turn_count": 0, "message_count": 0, "skills_used_count": 0, "distinct_skills_used_count": 0, "connectors_used_count": 0, "distinct_connectors_used_count": 0 }, "design_metrics": { "distinct_session_count": 0, "distinct_projects_used_count": 0, "distinct_projects_created_count": 0, "message_count": 0 }, "web_search_count": 0, "__date": "2026-05-14" } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_ai_user_usage", "description": "Per-user token usage across a date range", "title": "Claude AI User Usage", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Range Start (UTC)" }, "range_end": { "type": "string", "format": "date-time", "title": "Range End (UTC)" }, "user_id": { "type": "string", "title": "User ID" }, "user_name": { "type": "string", "title": "User Name" }, "email": { "type": "string", "format": "user", "title": "Email" }, "deleted": { "type": "boolean", "title": "Deleted User" }, "product": { "type": "string", "title": "Product" }, "model": { "type": "string", "title": "Model" }, "context_window": { "type": "string", "title": "Context Window" }, "inference_geo": { "type": "string", "title": "Inference Geo" }, "speed": { "type": "string", "title": "Speed" }, "uncached_input_tokens": { "type": "number", "title": "Uncached Input Tokens" }, "cache_creation_1h_tokens": { "type": "number", "title": "Cache Creation (1hr) Tokens" }, "cache_creation_5m_tokens": { "type": "number", "title": "Cache Creation (5min) Tokens" }, "cache_read_input_tokens": { "type": "number", "title": "Cache Read Input Tokens" }, "output_tokens": { "type": "number", "title": "Output Tokens" }, "total_tokens": { "type": "number", "title": "Total Tokens" }, "web_search_requests": { "type": "number", "title": "Web Search Requests" }, "requests": { "type": "number", "title": "Requests" } }, "required": [ "record_date" ] }, "mirrorProperties": {}, "calculationProperties": { "total_input_tokens": { "title": "Total Input Tokens", "description": "Sum of uncached input and cache read tokens", "calculation": ".properties.uncached_input_tokens + .properties.cache_read_input_tokens", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-ai-user-usage selector: query: 'true' startingAt: '2026-01-01T00:00:00Z' port: entity: mappings: identifier: >- .actor.user_id + "-" + .__starting_at[:10] + "-" + (.product // "all") + "-" + (.model // "all") title: (.actor.name // .actor.email // .actor.user_id) + " - " + .__starting_at[:10] blueprint: '"claude_ai_user_usage"' properties: record_date: .__starting_at range_end: .__ending_at user_id: .actor.user_id user_name: .actor.name email: .actor.email deleted: (.actor.deleted // false) product: .product model: .model context_window: .context_window inference_geo: .inference_geo speed: .speed uncached_input_tokens: (.uncached_input_tokens // 0) cache_creation_1h_tokens: (.cache_creation.ephemeral_1h_input_tokens // 0) cache_creation_5m_tokens: (.cache_creation.ephemeral_5m_input_tokens // 0) cache_read_input_tokens: (.cache_read_input_tokens // 0) output_tokens: (.output_tokens // 0) total_tokens: (.total_tokens // 0) web_search_requests: (.server_tool_use.web_search_requests // 0) requests: (.requests // 0) ``` Sample payloadJSON ``` { "product": null, "model": null, "context_window": null, "inference_geo": null, "speed": null, "slack_channel_id": null, "starting_at": null, "ending_at": null, "actor": { "type": "user_actor", "user_id": "user_01NeewegJNLKFDPGpYhvhus8", "name": "Oded Doek", "email": "dev@domain.io", "deleted": false }, "uncached_input_tokens": 889770, "cache_creation": { "ephemeral_1h_input_tokens": 10563402, "ephemeral_5m_input_tokens": 3777036 }, "cache_read_input_tokens": 734635224, "output_tokens": 2649149, "total_tokens": 752514581, "server_tool_use": { "web_search_requests": 22 }, "requests": 5048, "__starting_at": "2026-05-15T22:24:54Z", "__ending_at": "2026-06-15T22:24:54Z" } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_ai_user_cost", "description": "Per-user USD cost across a date range", "title": "Claude AI User Cost", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Range Start (UTC)" }, "range_end": { "type": "string", "format": "date-time", "title": "Range End (UTC)" }, "user_id": { "type": "string", "title": "User ID" }, "user_name": { "type": "string", "title": "User Name" }, "email": { "type": "string", "format": "user", "title": "Email" }, "deleted": { "type": "boolean", "title": "Deleted User" }, "product": { "type": "string", "title": "Product" }, "model": { "type": "string", "title": "Model" }, "currency": { "type": "string", "title": "Currency" }, "amount": { "type": "number", "title": "Discounted Amount" }, "list_amount": { "type": "number", "title": "List Amount" }, "requests": { "type": "number", "title": "Requests" } }, "required": [ "record_date" ] }, "mirrorProperties": {}, "calculationProperties": { "discount_amount": { "title": "Discount Amount", "description": "List price minus discounted price", "calculation": ".properties.list_amount - .properties.amount", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-ai-user-cost selector: query: 'true' startingAt: '2026-01-01T00:00:00Z' port: entity: mappings: identifier: >- .actor.user_id + "-" + .__starting_at[:10] + "-" + (.product // "all") + "-" + (.model // "all") title: (.actor.name // .actor.email // .actor.user_id) + " - " + .__starting_at[:10] blueprint: '"claude_ai_user_cost"' properties: record_date: .__starting_at range_end: .__ending_at user_id: .actor.user_id user_name: .actor.name email: .actor.email deleted: (.actor.deleted // false) product: .product model: .model currency: (.currency // "USD") amount: ((.amount // "0") | tonumber) / 100 list_amount: ((.list_amount // "0") | tonumber) / 100 requests: (.requests // 0) ``` Sample payloadJSON ``` { "product": null, "model": null, "context_window": null, "inference_geo": null, "speed": null, "slack_channel_id": null, "starting_at": null, "ending_at": null, "actor": { "type": "user_actor", "user_id": "user_01Vj49ahTgzCedehnXJBhatu", "name": "Aaron Taylor", "email": "dev@domain.io", "deleted": false }, "currency": "USD", "amount": "53865.538742", "list_amount": "54964.570145", "cost_type": null, "token_type": null, "requests": 6380, "__starting_at": "2026-05-15T22:25:03Z", "__ending_at": "2026-06-15T22:25:03Z" } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_ai_skill_usage", "description": "Org-level Claude skill usage metrics for a single day", "title": "Claude AI Skill Usage", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Record Date (UTC)" }, "skill_name": { "type": "string", "title": "Skill Name" }, "skill_display_name": { "type": "string", "title": "Skill Display Name" }, "distinct_user_count": { "type": "number", "title": "Distinct Users" }, "enable_count": { "type": "number", "title": "Enable Count" }, "invocation_count": { "type": "number", "title": "Invocation Count" }, "share_status": { "type": "string", "title": "Share Status" }, "currency": { "type": "string", "title": "Currency" }, "estimated_overage_spend": { "type": "number", "title": "Estimated Overage Spend" }, "attributed_list_price": { "type": "number", "title": "Attributed List Price" }, "chat_conversation_skill_used_count": { "type": "number", "title": "Chat Conversation Skill Uses" }, "code_session_skill_used_count": { "type": "number", "title": "Claude Code Session Skill Uses" }, "cowork_session_skill_used_count": { "type": "number", "title": "Cowork Session Skill Uses" }, "excel_session_skill_used_count": { "type": "number", "title": "Excel Session Skill Uses" }, "powerpoint_session_skill_used_count": { "type": "number", "title": "PowerPoint Session Skill Uses" }, "word_session_skill_used_count": { "type": "number", "title": "Word Session Skill Uses" }, "outlook_session_skill_used_count": { "type": "number", "title": "Outlook Session Skill Uses" } }, "required": [ "record_date", "skill_name" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-ai-skill-usage selector: query: 'true' timeFrame: 30 port: entity: mappings: identifier: >- ((.skill_name // "unknown") + "-" + .__date | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_")) title: ((.skill_display_name // .skill_name // "unknown") + " - " + .__date) blueprint: '"claude_ai_skill_usage"' properties: record_date: .__date + "T00:00:00Z" skill_name: (.skill_name // "unknown") skill_display_name: .skill_display_name distinct_user_count: .distinct_user_count enable_count: .enable_count invocation_count: .invocation_count share_status: .share_status currency: (.currency // "USD") estimated_overage_spend: (if .estimated_overage_spend then (.estimated_overage_spend | tonumber) / 100 else null end) attributed_list_price: (if .attributed_list_price then (.attributed_list_price | tonumber) / 100 else null end) chat_conversation_skill_used_count: .chat_metrics.distinct_conversation_skill_used_count code_session_skill_used_count: .claude_code_metrics.distinct_session_skill_used_count cowork_session_skill_used_count: .cowork_metrics.distinct_session_skill_used_count excel_session_skill_used_count: .office_metrics.excel.distinct_session_skill_used_count powerpoint_session_skill_used_count: .office_metrics.powerpoint.distinct_session_skill_used_count word_session_skill_used_count: .office_metrics.word.distinct_session_skill_used_count outlook_session_skill_used_count: .office_metrics.outlook.distinct_session_skill_used_count ``` Sample payloadJSON ``` { "skill_name": "pptx", "skill_display_name": null, "distinct_user_count": 1, "enable_count": 0, "invocation_count": 1, "share_status": "public", "currency": "USD", "estimated_overage_spend": "55.2334", "attributed_list_price": "55.2618", "chat_metrics": { "distinct_conversation_skill_used_count": 1 }, "claude_code_metrics": { "distinct_session_skill_used_count": 0 }, "office_metrics": { "excel": { "distinct_session_skill_used_count": 0 }, "powerpoint": { "distinct_session_skill_used_count": 0 }, "word": { "distinct_session_skill_used_count": 0 }, "outlook": { "distinct_session_skill_used_count": 0 } }, "cowork_metrics": { "distinct_session_skill_used_count": 0 }, "__date": "2026-07-11" } ``` claude-platform-usage-recordclaude-platform-cost-recordclaude-platform-usage-record (groupBy: workspace\_id)claude-platform-usage-record (groupBy: model)claude-platform-code-analytics BlueprintJSON Create in Port ``` { "identifier": "claude_platform_usage_record", "description": "Daily Claude Platform API usage metrics", "title": "Claude Platform Usage Record", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Record Date (UTC)" }, "uncached_input_tokens": { "type": "number", "title": "Uncached Input Tokens" }, "output_tokens": { "type": "number", "title": "Output Tokens" }, "cache_read_input_tokens": { "type": "number", "title": "Cache Read Input Tokens" }, "cache_creation_5m_tokens": { "type": "number", "title": "Cache Creation (5min) Tokens" }, "cache_creation_1h_tokens": { "type": "number", "title": "Cache Creation (1hr) Tokens" }, "web_search_requests": { "type": "number", "title": "Web Search Requests" } }, "required": [ "record_date" ] }, "mirrorProperties": {}, "calculationProperties": { "total_input_tokens": { "title": "Total Input Tokens", "description": "Sum of uncached input and cache read tokens", "calculation": ".properties.uncached_input_tokens + .properties.cache_read_input_tokens", "type": "number" }, "total_tokens": { "title": "Total Tokens", "description": "Sum of all input and output tokens", "calculation": ".properties.uncached_input_tokens + .properties.cache_read_input_tokens + .properties.output_tokens", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-platform-usage-record selector: query: .results | length > 0 startingDate: '2026-01-01T00:00:00Z' bucketWidth: 1d groupBy: [] port: entity: mappings: identifier: .starting_at[:10] + "-usage" title: '"Claude Usage - " + .starting_at[:10]' blueprint: '"claude_platform_usage_record"' properties: record_date: .starting_at uncached_input_tokens: (.results[0].uncached_input_tokens // 0) output_tokens: (.results[0].output_tokens // 0) cache_read_input_tokens: (.results[0].cache_read_input_tokens // 0) cache_creation_5m_tokens: (.results[0].cache_creation.ephemeral_5m_input_tokens // 0) cache_creation_1h_tokens: (.results[0].cache_creation.ephemeral_1h_input_tokens // 0) web_search_requests: (.results[0].server_tool_use.web_search_requests // 0) ``` Sample payloadJSON ``` { "starting_at": "2026-01-04T00:00:00Z", "ending_at": "2026-01-05T00:00:00Z", "results": [ { "uncached_input_tokens": 10158, "cache_creation": { "ephemeral_1h_input_tokens": 0, "ephemeral_5m_input_tokens": 183637 }, "cache_read_input_tokens": 1207221, "output_tokens": 16925, "server_tool_use": { "web_search_requests": 0 }, "api_key_id": null, "workspace_id": null, "account_id": null, "service_account_id": null, "model": null, "service_tier": null, "context_window": null, "inference_geo": null } ] } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_platform_cost_record", "description": "Daily cost tracking for Claude Platform API usage", "title": "Claude Platform Cost Record", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Record Date (UTC)" }, "amount": { "type": "number", "title": "Cost Amount" }, "currency": { "type": "string", "title": "Currency" } }, "required": [ "record_date" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-platform-cost-record selector: query: .results | length > 0 startingDate: '2026-01-01T00:00:00Z' bucketWidth: 1d port: entity: mappings: identifier: .starting_at[:10] + "-cost" title: '"Claude Cost - " + .starting_at[:10]' blueprint: '"claude_platform_cost_record"' properties: record_date: .starting_at amount: ((.results[0].amount // "0") | tonumber) currency: (.results[0].currency // "USD") ``` Sample payloadJSON ``` { "starting_at": "2026-01-01T00:00:00Z", "ending_at": "2026-01-02T00:00:00Z", "results": [ { "currency": "USD", "amount": "536.07566", "workspace_id": null, "description": null, "cost_type": null, "context_window": null, "model": null, "service_tier": null, "token_type": null, "inference_geo": null } ] } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_platform_workspace_usage", "description": "Usage metrics broken down by workspace", "title": "Claude Platform Workspace Usage", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Record Date (UTC)" }, "workspace_id": { "type": "string", "title": "Workspace ID" }, "uncached_input_tokens": { "type": "number", "title": "Uncached Input Tokens" }, "output_tokens": { "type": "number", "title": "Output Tokens" }, "cache_read_input_tokens": { "type": "number", "title": "Cache Read Input Tokens" }, "cache_creation_5m_tokens": { "type": "number", "title": "Cache Creation (5min) Tokens" }, "cache_creation_1h_tokens": { "type": "number", "title": "Cache Creation (1hr) Tokens" }, "web_search_requests": { "type": "number", "title": "Web Search Requests" } }, "required": [ "record_date", "workspace_id" ] }, "mirrorProperties": {}, "calculationProperties": { "total_tokens": { "title": "Total Tokens", "calculation": ".properties.uncached_input_tokens + .properties.cache_read_input_tokens + .properties.output_tokens", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-platform-usage-record selector: query: .results | length > 0 startingDate: '2026-01-01T00:00:00Z' bucketWidth: 1d groupBy: - workspace_id port: itemsToParse: .results itemsToParseTopLevelTransform: false entity: mappings: identifier: (.item.workspace_id // "default") + "-" + .starting_at[:10] title: (.item.workspace_id // "default") + " - " + .starting_at[:10] blueprint: '"claude_platform_workspace_usage"' properties: record_date: .starting_at workspace_id: (.item.workspace_id // "default") uncached_input_tokens: (.item.uncached_input_tokens // 0) output_tokens: (.item.output_tokens // 0) cache_read_input_tokens: (.item.cache_read_input_tokens // 0) cache_creation_5m_tokens: (.item.cache_creation.ephemeral_5m_input_tokens // 0) cache_creation_1h_tokens: (.item.cache_creation.ephemeral_1h_input_tokens // 0) web_search_requests: (.item.server_tool_use.web_search_requests // 0) ``` Sample payloadJSON ``` { "starting_at": "2026-01-01T00:00:00Z", "ending_at": "2026-01-02T00:00:00Z", "results": [ { "uncached_input_tokens": 163662, "cache_creation": { "ephemeral_1h_input_tokens": 0, "ephemeral_5m_input_tokens": 229562 }, "cache_read_input_tokens": 7287271, "output_tokens": 40898, "server_tool_use": { "web_search_requests": 0 }, "api_key_id": null, "workspace_id": "wrkspc_01R8i1bkxHiD5qiiY4SrhsRC", "account_id": null, "service_account_id": null, "model": null, "service_tier": null, "context_window": null, "inference_geo": null } ] } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_platform_model_usage", "description": "Usage metrics broken down by Claude model type", "title": "Claude Platform Model Usage", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Record Date (UTC)" }, "model": { "type": "string", "title": "Model" }, "uncached_input_tokens": { "type": "number", "title": "Uncached Input Tokens" }, "output_tokens": { "type": "number", "title": "Output Tokens" }, "cache_read_input_tokens": { "type": "number", "title": "Cache Read Input Tokens" }, "cache_creation_5m_tokens": { "type": "number", "title": "Cache Creation (5min) Tokens" }, "cache_creation_1h_tokens": { "type": "number", "title": "Cache Creation (1hr) Tokens" }, "web_search_requests": { "type": "number", "title": "Web Search Requests" } }, "required": [ "record_date", "model" ] }, "mirrorProperties": {}, "calculationProperties": { "total_tokens": { "title": "Total Tokens", "calculation": ".properties.uncached_input_tokens + .properties.cache_read_input_tokens + .properties.output_tokens", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-platform-usage-record selector: query: .results | length > 0 startingDate: '2026-01-01T00:00:00Z' bucketWidth: 1d groupBy: - model port: itemsToParse: .results itemsToParseTopLevelTransform: false entity: mappings: identifier: .item.model + "-" + .starting_at[:10] title: .item.model + " - " + .starting_at[:10] blueprint: '"claude_platform_model_usage"' properties: record_date: .starting_at model: .item.model uncached_input_tokens: (.item.uncached_input_tokens // 0) output_tokens: (.item.output_tokens // 0) cache_read_input_tokens: (.item.cache_read_input_tokens // 0) cache_creation_5m_tokens: (.item.cache_creation.ephemeral_5m_input_tokens // 0) cache_creation_1h_tokens: (.item.cache_creation.ephemeral_1h_input_tokens // 0) web_search_requests: (.item.server_tool_use.web_search_requests // 0) ``` Sample payloadJSON ``` { "starting_at": "2026-01-04T00:00:00Z", "ending_at": "2026-01-05T00:00:00Z", "results": [ { "uncached_input_tokens": 7678, "cache_creation": { "ephemeral_1h_input_tokens": 0, "ephemeral_5m_input_tokens": 35867 }, "cache_read_input_tokens": 74329, "output_tokens": 2435, "server_tool_use": { "web_search_requests": 0 }, "api_key_id": null, "workspace_id": null, "account_id": null, "service_account_id": null, "model": "claude-haiku-4-5-20251001", "service_tier": null, "context_window": null, "inference_geo": null }, { "uncached_input_tokens": 1629, "cache_creation": { "ephemeral_1h_input_tokens": 0, "ephemeral_5m_input_tokens": 133486 }, "cache_read_input_tokens": 1132892, "output_tokens": 14321, "server_tool_use": { "web_search_requests": 0 }, "api_key_id": null, "workspace_id": null, "account_id": null, "service_account_id": null, "model": "claude-opus-4-5-20251101", "service_tier": null, "context_window": null, "inference_geo": null }, { "uncached_input_tokens": 851, "cache_creation": { "ephemeral_1h_input_tokens": 0, "ephemeral_5m_input_tokens": 14284 }, "cache_read_input_tokens": 0, "output_tokens": 169, "server_tool_use": { "web_search_requests": 0 }, "api_key_id": null, "workspace_id": null, "account_id": null, "service_account_id": null, "model": "claude-sonnet-4-5-20250929", "service_tier": null, "context_window": null, "inference_geo": null } ] } ``` BlueprintJSON Create in Port ``` { "identifier": "claude_platform_code_analytics", "description": "Daily Claude Code usage metrics including sessions, lines of code, commits, and PRs", "title": "Claude Platform Code Analytics", "icon": "Claude", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Record Date (UTC)" }, "organization_id": { "type": "string", "title": "Organization ID" }, "actor_type": { "type": "string", "title": "Actor Type" }, "actor_name": { "type": "string", "title": "Actor Name" }, "terminal_type": { "type": "string", "title": "Terminal Type" }, "num_sessions": { "type": "number", "title": "Sessions" }, "lines_added": { "type": "number", "title": "Lines Added" }, "lines_removed": { "type": "number", "title": "Lines Removed" }, "commits": { "type": "number", "title": "Commits by Claude Code" }, "pull_requests": { "type": "number", "title": "Pull Requests by Claude Code" }, "edit_tool_accepted": { "type": "number", "title": "Edit Tool Accepted" }, "edit_tool_rejected": { "type": "number", "title": "Edit Tool Rejected" }, "write_tool_accepted": { "type": "number", "title": "Write Tool Accepted" }, "write_tool_rejected": { "type": "number", "title": "Write Tool Rejected" }, "models_used": { "type": "array", "items": { "type": "string" }, "title": "Models Used" }, "model_input_tokens": { "type": "number", "title": "Model Input Tokens" }, "model_output_tokens": { "type": "number", "title": "Model Output Tokens" }, "model_cache_read_tokens": { "type": "number", "title": "Model Cache Read Tokens" }, "model_cache_creation_tokens": { "type": "number", "title": "Model Cache Creation Tokens" }, "estimated_cost": { "type": "number", "title": "Estimated Cost (USD)" }, "currency": { "type": "string", "title": "Currency" } }, "required": [ "record_date", "organization_id" ] }, "mirrorProperties": {}, "calculationProperties": { "total_lines_changed": { "title": "Total Lines Changed", "calculation": ".properties.lines_added + .properties.lines_removed", "type": "number" }, "edit_acceptance_rate": { "title": "Edit Acceptance Rate", "calculation": "if (.properties.edit_tool_accepted + .properties.edit_tool_rejected) > 0 then (.properties.edit_tool_accepted / (.properties.edit_tool_accepted + .properties.edit_tool_rejected)) * 100 else 0 end", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: claude-platform-code-analytics selector: query: 'true' startingDate: '2026-01-01' port: entity: mappings: identifier: .organization_id + "-" + (.actor.api_key_name // .actor.email // "unknown") + "-" + .date[:10] title: (.actor.api_key_name // .actor.email // "unknown") + " - " + .date[:10] blueprint: '"claude_platform_code_analytics"' properties: record_date: .date organization_id: .organization_id actor_type: .actor.type actor_name: (.actor.api_key_name // .actor.email // "unknown") terminal_type: .terminal_type num_sessions: (.core_metrics.num_sessions // 0) lines_added: (.core_metrics.lines_of_code.added // 0) lines_removed: (.core_metrics.lines_of_code.removed // 0) commits: (.core_metrics.commits_by_claude_code // 0) pull_requests: (.core_metrics.pull_requests_by_claude_code // 0) edit_tool_accepted: (.tool_actions.edit_tool.accepted // 0) edit_tool_rejected: (.tool_actions.edit_tool.rejected // 0) write_tool_accepted: (.tool_actions.write_tool.accepted // 0) write_tool_rejected: (.tool_actions.write_tool.rejected // 0) models_used: ([(.model_breakdown // [])[].model] | unique) model_input_tokens: ([(.model_breakdown // [])[].tokens.input] | map(. // 0) | add) model_output_tokens: ([(.model_breakdown // [])[].tokens.output] | map(. // 0) | add) model_cache_read_tokens: ([(.model_breakdown // [])[].tokens.cache_read] | map(. // 0) | add) model_cache_creation_tokens: ([(.model_breakdown // [])[].tokens.cache_creation] | map(. // 0) | add) estimated_cost: ([(.model_breakdown // [])[].estimated_cost.amount] | map(. // 0) | add) currency: ((.model_breakdown // [])[0].estimated_cost.currency // "USD") ``` Sample payloadJSON ``` { "date": "2026-01-01T00:00:00Z", "actor": { "type": "api_actor", "api_key_name": "matan-tdd-agent" }, "organization_id": "6d236493-6408-4d45-bc06-c65f19c17ba9", "terminal_type": "iTerm.app", "customer_type": "api", "subscription_type": null, "core_metrics": { "num_sessions": 1, "lines_of_code": { "added": 508, "removed": 61 }, "commits_by_claude_code": 0, "pull_requests_by_claude_code": 0 }, "tool_actions": { "edit_tool": { "accepted": 10, "rejected": 0 }, "multi_edit_tool": { "accepted": 0, "rejected": 0 }, "write_tool": { "accepted": 3, "rejected": 0 }, "notebook_edit_tool": { "accepted": 0, "rejected": 0 } }, "model_breakdown": [ { "model": "claude-opus-4-5-20251101", "tokens": { "input": 1231, "output": 2207, "cache_read": 0, "cache_creation": 0 }, "estimated_cost": { "currency": "USD", "amount": 6 } }, { "model": "claude-opus-4-5", "tokens": { "input": 12, "output": 26406, "cache_read": 6494720, "cache_creation": 160361 }, "estimated_cost": { "currency": "USD", "amount": 491 } }, { "model": "claude-haiku-4-5-20251001", "tokens": { "input": 162419, "output": 12285, "cache_read": 792551, "cache_creation": 69201 }, "estimated_cost": { "currency": "USD", "amount": 39 } } ] } ``` ## Breaking down analytics on user and team level[​](#breaking-down-analytics-on-user-and-team-level "Direct link to Breaking down analytics on user and team level") The default `claude-ai-skill-usage` mapping stores org-level totals (one row per skill per day). You can break the same metrics out by user, team, or product by setting `groupBy` to one of `user_id`, `rbac_group_id`, or `product`. Use one dimension at a time. To ingest user- or team-level rows, add two properties to the existing `claude_ai_skill_usage` blueprint, then update the kind mapping. Grouped identifiers include the user or team, so they do not overwrite org-level records. ### Add blueprint properties[​](#add-blueprint-properties "Direct link to Add blueprint properties") 1. Go to your [data model](https://app.port.io/settings/data-model) page. 2. Find and expand the **Claude AI Skill Usage** (`claude_ai_skill_usage`) blueprint. 3. Click the `...` button, then **Edit Blueprint**. 4. Add the following properties to the blueprint `schema.properties` object, then click **Save**. ``` "user_id": { "type": "string", "title": "User ID" }, "rbac_group_id": { "type": "string", "title": "RBAC Group ID" }, "rbac_group_name": { "type": "string", "title": "RBAC Group Name" } ``` ### Update the mapping[​](#update-the-mapping "Direct link to Update the mapping") 1. Go to your [connectors](https://app.port.io/settings/data-sources) page. 2. Select your Claude AI integration, then open the **Mapping** tab. 3. Update the `claude-ai-skill-usage` kind: set `groupBy` to one of the three dimensions. Then click **Save & Resync**. * User * Team Set `groupBy` to `user_id` and map the `user_id` property: ``` - kind: claude-ai-skill-usage selector: query: 'true' timeFrame: 30 groupBy: - user_id port: entity: mappings: identifier: >- ((.skill_name // "unknown") + "-" + .__date + (if .user_id then "-" + .user_id else "" end) | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_")) title: >- ((.skill_display_name // .skill_name // "unknown") + " - " + .__date + (if .user_id then " - " + .user_id else "" end)) blueprint: '"claude_ai_skill_usage"' properties: record_date: .__date + "T00:00:00Z" skill_name: (.skill_name // "unknown") skill_display_name: .skill_display_name user_id: .user_id distinct_user_count: .distinct_user_count enable_count: .enable_count invocation_count: .invocation_count share_status: .share_status currency: (.currency // "USD") estimated_overage_spend: (if .estimated_overage_spend then (.estimated_overage_spend | tonumber) / 100 else null end) attributed_list_price: (if .attributed_list_price then (.attributed_list_price | tonumber) / 100 else null end) chat_conversation_skill_used_count: .chat_metrics.distinct_conversation_skill_used_count code_session_skill_used_count: .claude_code_metrics.distinct_session_skill_used_count cowork_session_skill_used_count: .cowork_metrics.distinct_session_skill_used_count excel_session_skill_used_count: .office_metrics.excel.distinct_session_skill_used_count powerpoint_session_skill_used_count: .office_metrics.powerpoint.distinct_session_skill_used_count word_session_skill_used_count: .office_metrics.word.distinct_session_skill_used_count outlook_session_skill_used_count: .office_metrics.outlook.distinct_session_skill_used_count ``` Set `groupBy` to `rbac_group_id` and map the `rbac_group_id` and `rbac_group_name` properties: ``` - kind: claude-ai-skill-usage selector: query: 'true' timeFrame: 30 groupBy: - rbac_group_id port: entity: mappings: identifier: >- ((.skill_name // "unknown") + "-" + .__date + (if .rbac_group_id then "-" + .rbac_group_id else "" end) | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_")) title: >- ((.skill_display_name // .skill_name // "unknown") + " - " + .__date + (if .rbac_group_name then " - " + .rbac_group_name elif .rbac_group_id then " - " + .rbac_group_id else "" end)) blueprint: '"claude_ai_skill_usage"' properties: record_date: .__date + "T00:00:00Z" skill_name: (.skill_name // "unknown") skill_display_name: .skill_display_name rbac_group_id: .rbac_group_id rbac_group_name: .rbac_group_name distinct_user_count: .distinct_user_count enable_count: .enable_count invocation_count: .invocation_count share_status: .share_status currency: (.currency // "USD") estimated_overage_spend: (if .estimated_overage_spend then (.estimated_overage_spend | tonumber) / 100 else null end) attributed_list_price: (if .attributed_list_price then (.attributed_list_price | tonumber) / 100 else null end) chat_conversation_skill_used_count: .chat_metrics.distinct_conversation_skill_used_count code_session_skill_used_count: .claude_code_metrics.distinct_session_skill_used_count cowork_session_skill_used_count: .cowork_metrics.distinct_session_skill_used_count excel_session_skill_used_count: .office_metrics.excel.distinct_session_skill_used_count powerpoint_session_skill_used_count: .office_metrics.powerpoint.distinct_session_skill_used_count word_session_skill_used_count: .office_metrics.word.distinct_session_skill_used_count outlook_session_skill_used_count: .office_metrics.outlook.distinct_session_skill_used_count ``` RBAC group overlap When grouping by `rbac_group_id`, a user is attributed to every group they held during that UTC day. Grouped rows are not an exclusive partition and can sum above org-level totals. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Migration guide[​](#migration-guide "Direct link to Migration guide") If you currently ingest Claude metrics through a custom Ocean integration, migrate to the dedicated Claude AI integration: 1. Create the new **Claude AI** connector from your [connectors page](https://app.port.io/settings/data-sources). 2. Copy your existing Claude API credentials into the new integration setup. 3. Move your custom blueprint and mapping configuration into the dedicated integration. 4. Run a sync and validate that records are ingested for all enabled resources. 5. Disable the legacy custom Ocean Claude integration after validation. ## Related pages[​](#related-pages "Direct link to Related pages") * [Skills usage analytics](/agent-management/ai-registry/skills/skills-usage-analytics/.md): high-level guide to analyzing skill adoption across Cursor and Claude. * [Visualize Claude metrics](/guides/all/visualize-claude-metrics/.md): dashboards for Claude usage and cost. --- # Cursor metrics ![Cursor icon](/img/guides/icons/Cursor.svg)![Cursor icon](/img/guides/icons/dark/Cursor.svg) Loading version... Port's Cursor integration allows you to ingest daily usage, usage events, model usage, and skill adoption analytics into your software catalog. Supported resources ![External link icon](/img/icons/external-link.svg)[cursor-daily-usage](https://cursor.com/docs/account/teams/admin-api#get-daily-usage-data)[cursor-usage-event](https://cursor.com/docs/account/teams/admin-api#get-usage-events-data)[cursor-team-model-usage](https://cursor.com/docs/account/teams/analytics-api#model-usage)[cursor-user-model-usage](https://cursor.com/docs/account/teams/analytics-api#model-usage-by-user)[cursor-team-skill-usage](https://cursor.com/docs/account/teams/analytics-api#skills-adoption) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A [Cursor API key](https://cursor.com/docs/api#creating-api-keys) with permissions to read organization or team usage metrics. * A Cursor Teams or Cursor Enterprise plan. Please refer to the [Cursor API availability docs](https://cursor.com/docs/api#available-apis) to see what is available with Teams and Enterprise licenses. * Your Port user role is set to `Admin`. ## Setup[​](#setup "Direct link to Setup") Choose one of the installation methods below. If you are not sure which option to use, review the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/overview/.md) to ingest data from third-party APIs into Port. The mapping uses the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to transform fields and shape incoming Cursor data into Port entities. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` resources: - kind: cursor-daily-usage selector: query: 'true' startDate: 30d endDate: 0d port: entity: mappings: identifier: (.userId + "-" + .day) title: (.email + " " + .day) blueprint: '"cursorDailyUsage"' properties: day: (if .day then (.day + "T00:00:00Z") else null end) user_id: .userId email: .email is_active: .isActive total_lines_added: .totalLinesAdded total_lines_deleted: .totalLinesDeleted accepted_lines_added: .acceptedLinesAdded accepted_lines_deleted: .acceptedLinesDeleted total_applies: .totalApplies total_accepts: .totalAccepts total_rejects: .totalRejects total_tabs_shown: .totalTabsShown total_tabs_accepted: .totalTabsAccepted composer_requests: .composerRequests chat_requests: .chatRequests agent_requests: .agentRequests most_used_model: .mostUsedModel subscription_included_reqs: .subscriptionIncludedReqs api_key_reqs: .apiKeyReqs usage_based_reqs: .usageBasedReqs bugbot_usages: .bugbotUsages - kind: cursor-usage-event selector: query: 'true' startDate: 30d endDate: 0d port: entity: mappings: identifier: ((.timestamp + "-" + .userEmail + "-" + .model + "-" + .kind) | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_")) title: (.userEmail + " " + .model + " " + .kind) blueprint: '"cursorUsageEvent"' properties: timestamp: (.timestamp | tonumber / 1000 | todate) model: .model kind: .kind max_mode: .maxMode requests_costs: .requestsCosts is_token_based_call: .isTokenBasedCall token_usage: .tokenUsage user_email: .userEmail cursor_token_fee: .cursorTokenFee is_headless: .isHeadless charged_amount: (if .chargedCents then (.chargedCents / 100) else null end) - kind: cursor-team-model-usage selector: query: 'true' startDate: 30d endDate: 0d port: itemsToParse: (.model_breakdown // {} | to_entries) entity: mappings: identifier: (.date + "-" + .item.key) title: (.date + " " + .item.key) blueprint: '"cursorTeamModelUsage"' properties: date: (if .date then (.date + "T00:00:00Z") else null end) model: .item.key messages: .item.value.messages users: .item.value.users - kind: cursor-user-model-usage selector: query: 'true' startDate: 30d endDate: 0d port: itemsToParse: (.model_breakdown // {} | to_entries) entity: mappings: identifier: (.userEmail + "-" + .date + "-" + .item.key) title: (.userEmail + " " + .item.key) blueprint: '"cursorUserModelUsage"' properties: date: (if .date then (.date + "T00:00:00Z") else null end) user_email: .userEmail model: .item.key messages: .item.value.messages - kind: cursor-team-skill-usage selector: query: 'true' startDate: 30d endDate: 0d port: entity: mappings: identifier: >- ((.event_date + "-" + (.skill_name // "unknown")) | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_")) title: ((.skill_name // "unknown") + " - " + .event_date) blueprint: '"cursorTeamSkillUsage"' properties: event_date: (if .event_date then (.event_date + "T00:00:00Z") else null end) skill_name: .skill_name usage: .usage ``` ### Advanced selector configuration[​](#advanced-selector-configuration "Direct link to Advanced selector configuration") * `query` applies to all Cursor resource kinds and defaults to `'true'`. * `startDate` is a relative start date in days, for example `7d`. The default is `30d`. This cannot exceed 30 days. * `endDate` is a relative end date in days, for example `0d` for now. The default is `0d`. * `users` is an optional comma-separated list of user emails or user IDs. It is supported on `cursor-team-skill-usage` and filters skill adoption metrics to the specified users. Below is an example usage: ``` - kind: cursor-daily-usage selector: query: 'true' startDate: 7d endDate: 0d ``` Filter skill usage to specific users: ``` - kind: cursor-team-skill-usage selector: query: 'true' startDate: 14d endDate: 0d users: alice@example.com,user_abc123 ``` ## Mapping and examples per resource[​](#mapping-and-examples-per-resource "Direct link to Mapping and examples per resource") To view and test the integration mapping against sample API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Cursor Enterprise required for analytics kinds The `cursor-team-model-usage`, `cursor-user-model-usage`, and `cursor-team-skill-usage` kinds rely on the Cursor Analytics API, which is available only on the Cursor Enterprise tier. Add these kinds to your mapping only if your Cursor license includes Enterprise analytics access. cursor-daily-usagecursor-usage-eventcursor-team-model-usagecursor-user-model-usagecursor-team-skill-usage BlueprintJSON Create in Port ``` { "identifier": "cursorDailyUsage", "description": "Daily team usage metrics from Cursor Admin API", "title": "Cursor Daily Usage", "icon": "Cursor", "schema": { "properties": { "day": { "type": "string", "format": "date-time", "title": "Day" }, "user_id": { "type": "string", "title": "User ID" }, "email": { "type": "string", "title": "User Email", "format": "email" }, "is_active": { "type": "boolean", "title": "Active" }, "total_lines_added": { "type": "number", "title": "Total Lines Added" }, "total_lines_deleted": { "type": "number", "title": "Total Lines Deleted" }, "accepted_lines_added": { "type": "number", "title": "Accepted Lines Added" }, "accepted_lines_deleted": { "type": "number", "title": "Accepted Lines Deleted" }, "total_applies": { "type": "number", "title": "Total Applies" }, "total_accepts": { "type": "number", "title": "Total Accepts" }, "total_rejects": { "type": "number", "title": "Total Rejects" }, "composer_requests": { "type": "number", "title": "Composer Requests" }, "chat_requests": { "type": "number", "title": "Chat Requests" }, "agent_requests": { "type": "number", "title": "Agent Requests" }, "most_used_model": { "type": "string", "title": "Most Used Model" }, "total_tabs_shown": { "type": "number", "title": "Total Tabs Shown" }, "total_tabs_accepted": { "type": "number", "title": "Total Tabs Accepted" }, "subscription_included_reqs": { "type": "number", "title": "Subscription Included Requests" }, "api_key_reqs": { "type": "number", "title": "API Key Requests" }, "usage_based_reqs": { "type": "number", "title": "Usage-based Requests" }, "bugbot_usages": { "type": "number", "title": "Bugbot Usages" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "acceptance_rate": { "title": "Acceptance Rate", "description": "Accepted AI suggestions as a percentage of accepted + rejected suggestions", "calculation": "if (.properties.total_accepts + .properties.total_rejects) > 0 then (.properties.total_accepts / (.properties.total_accepts + .properties.total_rejects)) * 100 else 0 end", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: cursor-daily-usage selector: query: 'true' startDate: 30d endDate: 0d port: entity: mappings: identifier: (.userId + "-" + .day) title: (.email + " " + .day) blueprint: '"cursorDailyUsage"' properties: day: (if .day then (.day + "T00:00:00Z") else null end) user_id: .userId email: .email is_active: .isActive total_lines_added: .totalLinesAdded total_lines_deleted: .totalLinesDeleted accepted_lines_added: .acceptedLinesAdded accepted_lines_deleted: .acceptedLinesDeleted total_applies: .totalApplies total_accepts: .totalAccepts total_rejects: .totalRejects total_tabs_shown: .totalTabsShown total_tabs_accepted: .totalTabsAccepted composer_requests: .composerRequests chat_requests: .chatRequests agent_requests: .agentRequests most_used_model: .mostUsedModel subscription_included_reqs: .subscriptionIncludedReqs api_key_reqs: .apiKeyReqs usage_based_reqs: .usageBasedReqs bugbot_usages: .bugbotUsages ``` Sample payloadJSON ``` { "date": 1778371200000, "day": "2026-05-10", "userId": "user_LGgY21zt4XsBFMZYi2VpfNdAUv", "email": "dev@getport.io", "isActive": true, "totalLinesAdded": 27, "totalLinesDeleted": 28, "acceptedLinesAdded": 27, "acceptedLinesDeleted": 28, "totalApplies": 2, "totalAccepts": 2, "totalRejects": 0, "totalTabsShown": 1, "totalTabsAccepted": 0, "composerRequests": 0, "chatRequests": 0, "agentRequests": 11, "cmdkUsages": 0, "subscriptionIncludedReqs": 11, "apiKeyReqs": 0, "usageBasedReqs": 0, "bugbotUsages": 0, "mostUsedModel": "claude-4.6-sonnet-medium-thinking", "applyMostUsedExtension": "py", "tabMostUsedExtension": "py", "clientVersion": "3.1.17" } ``` BlueprintJSON Create in Port ``` { "identifier": "cursorUsageEvent", "description": "Detailed Cursor usage events with model and cost metadata", "title": "Cursor Usage Event", "icon": "Cursor", "schema": { "properties": { "timestamp": { "type": "string", "title": "Timestamp", "format": "date-time" }, "model": { "type": "string", "title": "Model" }, "kind": { "type": "string", "title": "Kind" }, "max_mode": { "type": "boolean", "title": "Max Mode" }, "requests_costs": { "type": "number", "title": "Request Cost" }, "is_token_based_call": { "type": "boolean", "title": "Token Based Call" }, "token_usage": { "type": "object", "title": "Token Usage" }, "user_email": { "type": "string", "title": "User Email", "format": "email" }, "cursor_token_fee": { "type": "number", "title": "Cursor Token Fee" }, "is_headless": { "type": "boolean", "title": "Headless" }, "charged_amount": { "type": "number", "title": "Charged Amount (USD)" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: cursor-usage-event selector: query: 'true' startDate: 30d endDate: 0d port: entity: mappings: identifier: ((.timestamp + "-" + .userEmail + "-" + .model + "-" + .kind) | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_")) title: (.userEmail + " " + .model + " " + .kind) blueprint: '"cursorUsageEvent"' properties: timestamp: (.timestamp | tonumber / 1000 | todate) model: .model kind: .kind max_mode: .maxMode requests_costs: .requestsCosts is_token_based_call: .isTokenBasedCall token_usage: .tokenUsage user_email: .userEmail cursor_token_fee: .cursorTokenFee is_headless: .isHeadless charged_amount: (if .chargedCents then (.chargedCents / 100) else null end) ``` Sample payloadJSON ``` { "timestamp": "1781029736938", "model": "claude-4.6-opus-high-thinking", "kind": "Included in Business", "maxMode": false, "requestsCosts": 1.9, "isTokenBasedCall": true, "tokenUsage": { "inputTokens": 3961, "outputTokens": 1415, "cacheWriteTokens": 1245, "cacheReadTokens": 125256, "totalCents": 7.708795 }, "userEmail": "dev@getport.io", "cursorTokenFee": 4.351941, "isChargeable": true, "serviceAccountId": "null", "isHeadless": false, "chargedCents": 12.060736 } ``` BlueprintJSON Create in Port ``` { "identifier": "cursorTeamModelUsage", "description": "Daily team-level model usage from the Cursor Analytics API", "title": "Cursor Team Model Usage", "icon": "Cursor", "schema": { "properties": { "date": { "type": "string", "format": "date-time", "title": "Date" }, "model": { "type": "string", "title": "Model" }, "messages": { "type": "number", "title": "Messages" }, "users": { "type": "number", "title": "Users" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: cursor-team-model-usage selector: query: 'true' startDate: 30d endDate: 0d port: itemsToParse: (.model_breakdown // {} | to_entries) entity: mappings: identifier: (.date + "-" + .item.key) title: (.date + " " + .item.key) blueprint: '"cursorTeamModelUsage"' properties: date: (if .date then (.date + "T00:00:00Z") else null end) model: .item.key messages: .item.value.messages users: .item.value.users ``` Sample payloadJSON ``` { "date": "2026-05-10", "model_breakdown": { "claude-opus-4-7-thinking-high": { "messages": 18, "users": 1 }, "claude-opus-4-7-thinking-xhigh": { "messages": 17, "users": 3 }, "gpt-5.5-high": { "messages": 23, "users": 1 }, "composer-2-fast": { "messages": 90, "users": 15 }, "default": { "messages": 265, "users": 15 }, "claude-opus-4-7-thinking-medium": { "messages": 2, "users": 1 }, "claude-4.6-sonnet-medium": { "messages": 89, "users": 3 }, "claude-4.6-opus-high": { "messages": 2, "users": 2 }, "gpt-5.3-codex": { "messages": 40, "users": 2 }, "claude-4.6-opus-high-thinking": { "messages": 39, "users": 4 }, "claude-4.5-opus-high": { "messages": 3, "users": 1 }, "claude-4.5-sonnet": { "messages": 45, "users": 2 }, "github_bugbot": { "messages": 0, "users": 1 }, "composer-2": { "messages": 47, "users": 4 }, "claude-4.6-sonnet-medium-thinking": { "messages": 192, "users": 18 }, "claude-4.5-opus-high-thinking": { "messages": 43, "users": 6 }, "claude-4.5-sonnet-thinking": { "messages": 6, "users": 1 }, "gpt-5.5-medium": { "messages": 3, "users": 1 } } } ``` BlueprintJSON Create in Port ``` { "identifier": "cursorUserModelUsage", "description": "Daily per-user model usage from the Cursor Analytics API", "title": "Cursor User Model Usage", "icon": "Cursor", "schema": { "properties": { "date": { "type": "string", "format": "date-time", "title": "Date" }, "user_email": { "type": "string", "format": "email", "title": "User Email" }, "model": { "type": "string", "title": "Model" }, "messages": { "type": "number", "title": "Messages" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: cursor-user-model-usage selector: query: 'true' startDate: 30d endDate: 0d port: itemsToParse: (.model_breakdown // {} | to_entries) entity: mappings: identifier: (.userEmail + "-" + .date + "-" + .item.key) title: (.userEmail + " " + .item.key) blueprint: '"cursorUserModelUsage"' properties: date: (if .date then (.date + "T00:00:00Z") else null end) user_email: .userEmail model: .item.key messages: .item.value.messages ``` Sample payloadJSON ``` { "userEmail": "dev@getport.io", "date": "2026-06-01", "model_breakdown": { "default": { "messages": 15 }, "composer-2.5": { "messages": 3 } } } ``` BlueprintJSON Create in Port ``` { "identifier": "cursorTeamSkillUsage", "description": "Daily team-level skill adoption metrics from the Cursor Analytics API", "title": "Cursor Team Skill Usage", "icon": "Cursor", "schema": { "properties": { "event_date": { "type": "string", "format": "date-time", "title": "Event Date" }, "skill_name": { "type": "string", "title": "Skill Name" }, "usage": { "type": "number", "title": "Usage Count" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: cursor-team-skill-usage selector: query: 'true' startDate: 30d endDate: 0d port: entity: mappings: identifier: >- ((.event_date + "-" + (.skill_name // "unknown")) | gsub("[^A-Za-z0-9@_.+:\\\\/='-]"; "_")) title: ((.skill_name // "unknown") + " - " + .event_date) blueprint: '"cursorTeamSkillUsage"' properties: event_date: (if .event_date then (.event_date + "T00:00:00Z") else null end) skill_name: .skill_name usage: .usage ``` Sample payloadJSON ``` { "event_date": "2026-08-08", "skill_name": "nr-investigate", "usage": 9 } ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Migration guide[​](#migration-guide "Direct link to Migration guide") If you currently ingest Cursor metrics through a custom Ocean integration, migrate to the dedicated Cursor integration: 1. Create the new **Cursor** connector from your [connectors page](https://app.port.io/settings/data-sources). 2. Copy your existing Cursor API credentials into the new integration setup. 3. Replace legacy custom kinds with the native kinds: `cursor-daily-usage`, `cursor-usage-event`, `cursor-team-model-usage`, `cursor-user-model-usage`, and `cursor-team-skill-usage`. 4. Run a sync and validate that records are ingested for all enabled resources. 5. Disable the legacy custom Ocean Cursor integration after validation. ## Related pages[​](#related-pages "Direct link to Related pages") * [Skills usage analytics](/agent-management/ai-registry/skills/skills-usage-analytics/.md): high-level guide to analyzing skill adoption across Cursor and Claude. * [Visualize Cursor metrics](/guides/all/visualize-cursor-metrics/.md): dashboards for Cursor adoption and impact. --- # GitHub Copilot metrics ![GithubCopilot icon](/img/guides/icons/GithubCopilot.svg)![GithubCopilot icon](/img/guides/icons/dark/GithubCopilot.svg) Loading version... Port's GitHub Copilot integration allows you to ingest organization-level and enterprise-level GitHub Copilot usage metrics into your software catalog. Supported resources ![External link icon](/img/icons/external-link.svg)[organization-usage-metrics](https://docs.github.com/en/rest/copilot/copilot-usage-metrics)[user-usage-metrics](https://docs.github.com/en/enterprise-cloud@latest/rest/copilot/copilot-usage-metrics?apiVersion=2026-03-10#get-copilot-users-usage-metrics)[organization-user-usage-metrics](https://docs.github.com/en/enterprise-cloud@latest/rest/copilot/copilot-usage-metrics?apiVersion=2026-03-10#get-copilot-users-usage-metrics)[enterprise-usage-metrics](https://docs.github.com/en/enterprise-cloud@latest/rest/copilot/copilot-usage-metrics?apiVersion=2026-03-10#get-copilot-enterprise-usage-metrics)[enterprise-user-usage-metrics](https://docs.github.com/en/enterprise-cloud@latest/rest/copilot/copilot-usage-metrics?apiVersion=2026-03-10#get-copilot-users-usage-metrics) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### GitHub token[​](#github-token "Direct link to GitHub token") Port's GitHub Copilot integration requires a classic GitHub token **generated by organization owners or parent enterprise owners and billing managers** with at least one of the following scopes enabled: * `manage_billing:copilot` * `read:org` * `read:enterprise` In addition, the Copilot Metrics API access policy must be enabled for the organization within GitHub settings. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` entityDeletionThreshold: 0 resources: - kind: organization-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.__organization.login + "@" + .day) title: (.__organization.login + " copilot-metrics " + .day) blueprint: '"githubCopilotOrganizationUsage"' properties: record_date: .day + "T00:00:00Z" daily_active_users: .daily_active_users weekly_active_users: .weekly_active_users monthly_active_chat_users: .monthly_active_chat_users monthly_active_agent_users: .monthly_active_agent_users user_initiated_interaction_count: .user_initiated_interaction_count code_generation_activity_count: .code_generation_activity_count code_acceptance_activity_count: .code_acceptance_activity_count loc_suggested_to_add_sum: .loc_suggested_to_add_sum loc_added_sum: .loc_added_sum loc_suggested_to_delete_sum: .loc_suggested_to_delete_sum loc_deleted_sum: .loc_deleted_sum prs_created_by_copilot: .pull_requests.total_created_by_copilot prs_reviewed_by_copilot: .pull_requests.total_reviewed_by_copilot prs_merged_by_copilot: .pull_requests.total_merged_created_by_copilot total_copilot_applied_suggestions: .pull_requests.total_copilot_applied_suggestions git_hub_org: .__organization.login - kind: organization-user-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.user_login + "@" + .__organization.login + "@" + .day) title: (.user_login + "@" + .__organization.login + "@" + .day) blueprint: '"githubCopilotUserUsage"' properties: record_date: .day + "T00:00:00Z" code_generation_activity_count: .code_generation_activity_count code_acceptance_activity_count: .code_acceptance_activity_count loc_suggested_to_add_sum: .loc_suggested_to_add_sum loc_suggested_to_delete_sum: .loc_suggested_to_delete_sum loc_added_sum: .loc_added_sum loc_deleted_sum: .loc_deleted_sum user_initiated_interaction_count: .user_initiated_interaction_count git_hub_org: .__organization.login used_agent: .used_agent used_chat: .used_chat username: .user_login - kind: enterprise-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.__enterprise.slug + "@" + .day) title: (.__enterprise.slug + " copilot-metrics " + .day) blueprint: '"githubCopilotOrganizationUsage"' properties: record_date: .day + "T00:00:00Z" daily_active_users: .daily_active_users weekly_active_users: .weekly_active_users monthly_active_agent_users: .monthly_active_agent_users monthly_active_chat_users: .monthly_active_chat_users user_initiated_interaction_count: .user_initiated_interaction_count code_generation_activity_count: .code_generation_activity_count code_acceptance_activity_count: .code_acceptance_activity_count loc_suggested_to_add_sum: .loc_suggested_to_add_sum loc_added_sum: .loc_added_sum loc_suggested_to_delete_sum: .loc_suggested_to_delete_sum loc_deleted_sum: .loc_deleted_sum prs_created_by_copilot: .pull_requests.total_created_by_copilot prs_reviewed_by_copilot: .pull_requests.total_reviewed_by_copilot prs_merged_by_copilot: .pull_requests.total_merged_created_by_copilot total_copilot_applied_suggestions: .pull_requests.total_copilot_applied_suggestions - kind: enterprise-user-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.user_login + "@" + .__enterprise.slug + "@" + .day) title: (.user_login + "@" + .__enterprise.slug + "@" + .day) blueprint: '"githubCopilotUserUsage"' properties: record_date: .day + "T00:00:00Z" code_generation_activity_count: .code_generation_activity_count code_acceptance_activity_count: .code_acceptance_activity_count loc_suggested_to_add_sum: .loc_suggested_to_add_sum loc_suggested_to_delete_sum: .loc_suggested_to_delete_sum loc_added_sum: .loc_added_sum loc_deleted_sum: .loc_deleted_sum user_initiated_interaction_count: .user_initiated_interaction_count git_hub_org: .__enterprise.slug used_agent: .used_agent used_chat: .used_chat username: .user_login relations: github_team: combinator: '"and"' rules: - property: '"member_logins"' operator: '"contains"' value: .user_login ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to 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 [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. organization-usage-metricsorganization-user-usage-metricsenterprise-usage-metricsenterprise-user-usage-metrics BlueprintJSON Create in Port ``` { "identifier": "githubCopilotOrganizationUsage", "title": "Github Copilot Org Usage", "icon": "Github", "schema": { "properties": { "record_date": { "title": "Record Date", "type": "string", "format": "date-time" }, "daily_active_users": { "title": "Daily Active Users", "type": "number" }, "weekly_active_users": { "title": "Weekly Active Users", "type": "number" }, "monthly_active_users": { "title": "Monthly Active Users", "type": "number" }, "monthly_active_chat_users": { "title": "Monthly Active Chat Users", "type": "number" }, "monthly_active_agent_users": { "title": "Monthly Active Agent Users", "type": "number" }, "user_initiated_interaction_count": { "title": "User Initiated Interaction Count", "type": "number" }, "code_generation_activity_count": { "title": "Code Generation Activity Count", "type": "number" }, "code_acceptance_activity_count": { "title": "Code Acceptance Activity Count", "type": "number" }, "loc_suggested_to_add_sum": { "title": "LOC Suggested To Add", "type": "number" }, "loc_added_sum": { "title": "LOC Added", "type": "number" }, "loc_suggested_to_delete_sum": { "title": "LOC Suggested To Delete", "type": "number" }, "loc_deleted_sum": { "title": "LOC Deleted", "type": "number" }, "prs_created_by_copilot": { "title": "PRs Created by Copilot", "icon": "DefaultProperty", "type": "number" }, "prs_reviewed_by_copilot": { "title": "PRs Reviewed By Copilot", "icon": "DefaultProperty", "type": "number" }, "prs_merged_by_copilot": { "title": "PRs Merged By Copilot", "icon": "DefaultProperty", "type": "number" }, "total_copilot_applied_suggestions": { "title": "Total Suggestions Applied By Copilot", "type": "number" }, "git_hub_org": { "title": "GitHub Org", "type": "string" }, "daily_active_cli_users": { "title": "Daily Active CLI Users", "type": "number" }, "daily_active_copilot_cloud_agent_users": { "title": "Daily Active Cloud Agent Users", "type": "number" }, "weekly_active_copilot_cloud_agent_users": { "title": "Weekly Active Cloud Agent Users", "type": "number" }, "monthly_active_copilot_cloud_agent_users": { "title": "Monthly Active Cloud Agent Users", "type": "number" }, "daily_active_copilot_code_review_users": { "title": "Daily Active Code Review Users", "type": "number" }, "weekly_active_copilot_code_review_users": { "title": "Weekly Active Code Review Users", "type": "number" }, "monthly_active_copilot_code_review_users": { "title": "Monthly Active Code Review Users", "type": "number" }, "daily_passive_copilot_code_review_users": { "title": "Daily Passive Code Review Users", "type": "number" }, "weekly_passive_copilot_code_review_users": { "title": "Weekly Passive Code Review Users", "type": "number" }, "monthly_passive_copilot_code_review_users": { "title": "Monthly Passive Code Review Users", "type": "number" }, "totals_by_ide": { "title": "Totals by IDE", "type": "array" }, "totals_by_feature": { "title": "Totals by Feature", "type": "array" }, "totals_by_language_feature": { "title": "Totals by Language & Feature", "type": "array" }, "totals_by_language_model": { "title": "Totals by Language & Model", "type": "array" }, "totals_by_model_feature": { "title": "Totals by Model & Feature", "type": "array" }, "totals_by_ai_adoption_phase": { "title": "Totals by AI Adoption Phase", "type": "array" }, "pull_requests": { "title": "Pull Requests", "type": "object" }, "totals_by_cli": { "title": "Totals by CLI", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "acceptance_rate": { "title": "Acceptance Rate", "icon": "DefaultProperty", "calculation": "if (.properties.code_generation_activity_count == 0) then 0 else ((.properties.code_acceptance_activity_count / .properties.code_generation_activity_count) * 100 | round) end", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: organization-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.__organization.login + "@" + .day) blueprint: '"githubCopilotOrganizationUsage"' title: (.__organization.login + " copilot-metrics " + .day) properties: record_date: .day + "T00:00:00Z" daily_active_users: .daily_active_users weekly_active_users: .weekly_active_users monthly_active_users: .monthly_active_users monthly_active_chat_users: .monthly_active_chat_users monthly_active_agent_users: .monthly_active_agent_users daily_active_cli_users: .daily_active_cli_users daily_active_copilot_cloud_agent_users: .daily_active_copilot_cloud_agent_users weekly_active_copilot_cloud_agent_users: .weekly_active_copilot_cloud_agent_users monthly_active_copilot_cloud_agent_users: .monthly_active_copilot_cloud_agent_users daily_active_copilot_code_review_users: .daily_active_copilot_code_review_users weekly_active_copilot_code_review_users: .weekly_active_copilot_code_review_users monthly_active_copilot_code_review_users: .monthly_active_copilot_code_review_users daily_passive_copilot_code_review_users: .daily_passive_copilot_code_review_users weekly_passive_copilot_code_review_users: .weekly_passive_copilot_code_review_users monthly_passive_copilot_code_review_users: .monthly_passive_copilot_code_review_users user_initiated_interaction_count: .user_initiated_interaction_count code_generation_activity_count: .code_generation_activity_count code_acceptance_activity_count: .code_acceptance_activity_count loc_suggested_to_add_sum: .loc_suggested_to_add_sum loc_added_sum: .loc_added_sum loc_suggested_to_delete_sum: .loc_suggested_to_delete_sum loc_deleted_sum: .loc_deleted_sum prs_created_by_copilot: .pull_requests.total_created_by_copilot prs_reviewed_by_copilot: .pull_requests.total_reviewed_by_copilot prs_merged_by_copilot: .pull_requests.total_merged_created_by_copilot total_copilot_applied_suggestions: .pull_requests.total_copilot_applied_suggestions git_hub_org: .__organization.login totals_by_ide: .totals_by_ide totals_by_feature: .totals_by_feature totals_by_language_feature: .totals_by_language_feature totals_by_language_model: .totals_by_language_model totals_by_model_feature: .totals_by_model_feature totals_by_ai_adoption_phase: .totals_by_ai_adoption_phase pull_requests: .pull_requests totals_by_cli: .totals_by_cli ``` Sample payloadJSON ``` { "day": "2026-03-05", "organization_id": "216844958", "enterprise_id": "232528", "daily_active_users": 3, "weekly_active_users": 5, "monthly_active_users": 5, "monthly_active_chat_users": 4, "monthly_active_agent_users": 3, "user_initiated_interaction_count": 0, "code_generation_activity_count": 187, "code_acceptance_activity_count": 48, "totals_by_ide": [ { "ide": "neovim", "user_initiated_interaction_count": 0, "code_generation_activity_count": 185, "code_acceptance_activity_count": 47, "loc_suggested_to_add_sum": 250, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 66, "loc_deleted_sum": 0 }, { "ide": "intellij", "user_initiated_interaction_count": 0, "code_generation_activity_count": 2, "code_acceptance_activity_count": 1, "loc_suggested_to_add_sum": 2, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 1, "loc_deleted_sum": 0 } ], "totals_by_feature": [ { "feature": "code_completion", "user_initiated_interaction_count": 0, "code_generation_activity_count": 187, "code_acceptance_activity_count": 48, "loc_suggested_to_add_sum": 252, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 67, "loc_deleted_sum": 0 } ], "totals_by_language_feature": [ { "language": "python", "feature": "code_completion", "code_generation_activity_count": 167, "code_acceptance_activity_count": 47, "loc_suggested_to_add_sum": 232, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 66, "loc_deleted_sum": 0 }, { "language": "dotenv", "feature": "code_completion", "code_generation_activity_count": 5, "code_acceptance_activity_count": 1, "loc_suggested_to_add_sum": 5, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 1, "loc_deleted_sum": 0 }, { "language": "markdown", "feature": "code_completion", "code_generation_activity_count": 6, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 6, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0 }, { "language": "yaml", "feature": "code_completion", "code_generation_activity_count": 5, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 5, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0 }, { "language": "typescript", "feature": "code_completion", "code_generation_activity_count": 3, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 3, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0 }, { "language": "toml", "feature": "code_completion", "code_generation_activity_count": 1, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 1, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0 } ], "totals_by_language_model": [], "totals_by_model_feature": [], "loc_suggested_to_add_sum": 252, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 67, "loc_deleted_sum": 0, "pull_requests": { "total_reviewed": 0, "total_created": 0, "total_created_by_copilot": 0, "total_reviewed_by_copilot": 0, "total_merged": 0, "total_suggestions": 0, "total_applied_suggestions": 0, "total_merged_created_by_copilot": 0, "total_copilot_suggestions": 0, "total_copilot_applied_suggestions": 0 }, "__organization": { "login": "idp-app-dev", "id": 216844958, "node_id": "O_kgDODOzKng", "url": "https://api.github.com/orgs/idp-app-dev", "repos_url": "https://api.github.com/orgs/idp-app-dev/repos", "events_url": "https://api.github.com/orgs/idp-app-dev/events", "hooks_url": "https://api.github.com/orgs/idp-app-dev/hooks", "issues_url": "https://api.github.com/orgs/idp-app-dev/issues", "members_url": "https://api.github.com/orgs/idp-app-dev/members{/member}", "public_members_url": "https://api.github.com/orgs/idp-app-dev/public_members{/member}", "avatar_url": "https://avatars.githubusercontent.com/u/216844958?v=4", "description": "" } } ``` BlueprintJSON Create in Port ``` { "identifier": "githubCopilotUserUsage", "title": "GitHub Copilot User Usage", "icon": "Github", "schema": { "properties": { "record_date": { "type": "string", "title": "Record Date", "format": "date-time" }, "user_initiated_interaction_count": { "type": "number", "title": "User Initiated Interaction Count" }, "code_generation_activity_count": { "type": "number", "title": "Code Generation Activity Count" }, "code_acceptance_activity_count": { "type": "number", "title": "Code Acceptance Activity Count" }, "loc_suggested_to_add_sum": { "type": "number", "title": "LOC Suggested To Add" }, "loc_suggested_to_delete_sum": { "type": "number", "title": "LOC Suggested To Delete" }, "loc_added_sum": { "type": "number", "title": "LOC Added" }, "loc_deleted_sum": { "type": "number", "title": "LOC Deleted" }, "git_hub_org": { "type": "string", "title": "GitHub Org" }, "username": { "type": "string", "title": "Username" }, "used_agent": { "type": "boolean", "title": "Used Agent" }, "used_chat": { "type": "boolean", "title": "Used Chat" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "acceptance_rate": { "title": "Acceptance Rate", "icon": "DefaultProperty", "calculation": "if (.properties.code_generation_activity_count == 0) then 0 else ((.properties.code_acceptance_activity_count / .properties.code_generation_activity_count) * 100 | round) end", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: organization-user-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.user_login + "@" + .__organization.login + "@" + .day) title: (.user_login + "@" + .__organization.login + "@" + .day) blueprint: '"githubCopilotUserUsage"' properties: record_date: .day + "T00:00:00Z" code_generation_activity_count: .code_generation_activity_count code_acceptance_activity_count: .code_acceptance_activity_count loc_suggested_to_add_sum: .loc_suggested_to_add_sum loc_suggested_to_delete_sum: .loc_suggested_to_delete_sum loc_added_sum: .loc_added_sum loc_deleted_sum: .loc_deleted_sum user_initiated_interaction_count: .user_initiated_interaction_count git_hub_org: .__organization.login used_agent: .used_agent used_chat: .used_chat username: .user_login ``` Sample payloadJSON ``` { "report_start_day": "2026-02-27", "report_end_day": "2026-03-26", "day": "2026-03-16", "organization_id": "216844958", "enterprise_id": "232528", "user_id": 24739630, "user_login": "johnsmith", "user_initiated_interaction_count": 0, "code_generation_activity_count": 53, "code_acceptance_activity_count": 6, "totals_by_ide": [ { "ide": "neovim", "user_initiated_interaction_count": 0, "code_generation_activity_count": 53, "code_acceptance_activity_count": 6, "loc_suggested_to_add_sum": 71, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 6, "loc_deleted_sum": 0, "last_known_plugin_version": { "sampled_at": "2026-03-16T21:19:46.369Z", "plugin": "copilot.vim", "plugin_version": "1.59.0" }, "last_known_ide_version": { "sampled_at": "2026-03-16T21:19:46.369Z", "ide_version": "0.11.5" } } ], "totals_by_feature": [ { "feature": "code_completion", "user_initiated_interaction_count": 0, "code_generation_activity_count": 53, "code_acceptance_activity_count": 6, "loc_suggested_to_add_sum": 71, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 6, "loc_deleted_sum": 0 } ], "totals_by_language_feature": [ { "language": "python", "feature": "code_completion", "code_generation_activity_count": 19, "code_acceptance_activity_count": 3, "loc_suggested_to_add_sum": 24, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 3, "loc_deleted_sum": 0 }, { "language": "lua", "feature": "code_completion", "code_generation_activity_count": 20, "code_acceptance_activity_count": 1, "loc_suggested_to_add_sum": 31, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 1, "loc_deleted_sum": 0 }, { "language": "markdown", "feature": "code_completion", "code_generation_activity_count": 12, "code_acceptance_activity_count": 2, "loc_suggested_to_add_sum": 14, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 2, "loc_deleted_sum": 0 }, { "language": "dotenv", "feature": "code_completion", "code_generation_activity_count": 2, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 2, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0 } ], "totals_by_language_model": [], "totals_by_model_feature": [], "used_agent": false, "used_chat": false, "loc_suggested_to_add_sum": 71, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 6, "loc_deleted_sum": 0, "__organization": { "login": "idp-app-dev", "id": 216844958, "node_id": "O_kgDODOzKng", "url": "https://api.github.com/orgs/idp-app-dev", "repos_url": "https://api.github.com/orgs/idp-app-dev/repos", "events_url": "https://api.github.com/orgs/idp-app-dev/events", "hooks_url": "https://api.github.com/orgs/idp-app-dev/hooks", "issues_url": "https://api.github.com/orgs/idp-app-dev/issues", "members_url": "https://api.github.com/orgs/idp-app-dev/members{/member}", "public_members_url": "https://api.github.com/orgs/idp-app-dev/public_members{/member}", "avatar_url": "https://avatars.githubusercontent.com/u/216844958?v=4", "description": "" } } ``` BlueprintJSON Create in Port ``` { "identifier": "githubCopilotOrganizationUsage", "title": "Github Copilot Org Usage", "icon": "Github", "schema": { "properties": { "record_date": { "title": "Record Date", "type": "string", "format": "date-time" }, "daily_active_users": { "title": "Daily Active Users", "type": "number" }, "weekly_active_users": { "title": "Weekly Active Users", "type": "number" }, "monthly_active_users": { "title": "Monthly Active Users", "type": "number" }, "monthly_active_chat_users": { "title": "Monthly Active Chat Users", "type": "number" }, "monthly_active_agent_users": { "title": "Monthly Active Agent Users", "type": "number" }, "user_initiated_interaction_count": { "title": "User Initiated Interaction Count", "type": "number" }, "code_generation_activity_count": { "title": "Code Generation Activity Count", "type": "number" }, "code_acceptance_activity_count": { "title": "Code Acceptance Activity Count", "type": "number" }, "loc_suggested_to_add_sum": { "title": "LOC Suggested To Add", "type": "number" }, "loc_added_sum": { "title": "LOC Added", "type": "number" }, "loc_suggested_to_delete_sum": { "title": "LOC Suggested To Delete", "type": "number" }, "loc_deleted_sum": { "title": "LOC Deleted", "type": "number" }, "prs_created_by_copilot": { "title": "PRs Created by Copilot", "icon": "DefaultProperty", "type": "number" }, "prs_reviewed_by_copilot": { "title": "PRs Reviewed By Copilot", "icon": "DefaultProperty", "type": "number" }, "prs_merged_by_copilot": { "title": "PRs Merged By Copilot", "icon": "DefaultProperty", "type": "number" }, "total_copilot_applied_suggestions": { "title": "Total Suggestions Applied By Copilot", "type": "number" }, "git_hub_org": { "title": "GitHub Org", "type": "string" }, "daily_active_cli_users": { "title": "Daily Active CLI Users", "type": "number" }, "daily_active_copilot_cloud_agent_users": { "title": "Daily Active Cloud Agent Users", "type": "number" }, "weekly_active_copilot_cloud_agent_users": { "title": "Weekly Active Cloud Agent Users", "type": "number" }, "monthly_active_copilot_cloud_agent_users": { "title": "Monthly Active Cloud Agent Users", "type": "number" }, "daily_active_copilot_code_review_users": { "title": "Daily Active Code Review Users", "type": "number" }, "weekly_active_copilot_code_review_users": { "title": "Weekly Active Code Review Users", "type": "number" }, "monthly_active_copilot_code_review_users": { "title": "Monthly Active Code Review Users", "type": "number" }, "daily_passive_copilot_code_review_users": { "title": "Daily Passive Code Review Users", "type": "number" }, "weekly_passive_copilot_code_review_users": { "title": "Weekly Passive Code Review Users", "type": "number" }, "monthly_passive_copilot_code_review_users": { "title": "Monthly Passive Code Review Users", "type": "number" }, "totals_by_ide": { "title": "Totals by IDE", "type": "array" }, "totals_by_feature": { "title": "Totals by Feature", "type": "array" }, "totals_by_language_feature": { "title": "Totals by Language & Feature", "type": "array" }, "totals_by_language_model": { "title": "Totals by Language & Model", "type": "array" }, "totals_by_model_feature": { "title": "Totals by Model & Feature", "type": "array" }, "totals_by_ai_adoption_phase": { "title": "Totals by AI Adoption Phase", "type": "array" }, "pull_requests": { "title": "Pull Requests", "type": "object" }, "totals_by_cli": { "title": "Totals by CLI", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "acceptance_rate": { "title": "Acceptance Rate", "icon": "DefaultProperty", "calculation": "if (.properties.code_generation_activity_count == 0) then 0 else ((.properties.code_acceptance_activity_count / .properties.code_generation_activity_count) * 100 | round) end", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: enterprise-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.__enterprise.slug + "@" + .day) title: (.__enterprise.slug + " copilot-metrics " + .day) blueprint: '"githubCopilotOrganizationUsage"' properties: record_date: .day + "T00:00:00Z" daily_active_users: .daily_active_users weekly_active_users: .weekly_active_users monthly_active_agent_users: .monthly_active_agent_users monthly_active_chat_users: .monthly_active_chat_users user_initiated_interaction_count: .user_initiated_interaction_count code_generation_activity_count: .code_generation_activity_count code_acceptance_activity_count: .code_acceptance_activity_count loc_suggested_to_add_sum: .loc_suggested_to_add_sum loc_added_sum: .loc_added_sum loc_suggested_to_delete_sum: .loc_suggested_to_delete_sum loc_deleted_sum: .loc_deleted_sum prs_created_by_copilot: .pull_requests.total_created_by_copilot prs_reviewed_by_copilot: .pull_requests.total_reviewed_by_copilot prs_merged_by_copilot: .pull_requests.total_merged_created_by_copilot total_copilot_applied_suggestions: .pull_requests.total_copilot_applied_suggestions ``` Sample payloadJSON ``` { "day": "2026-04-06", "enterprise_id": "232528", "daily_active_users": 5, "weekly_active_users": 6, "monthly_active_users": 7, "monthly_active_chat_users": 1, "monthly_active_agent_users": 1, "user_initiated_interaction_count": 0, "code_generation_activity_count": 151, "code_acceptance_activity_count": 39, "totals_by_ide": [ { "ide": "intellij", "user_initiated_interaction_count": 0, "code_generation_activity_count": 133, "code_acceptance_activity_count": 33, "loc_suggested_to_add_sum": 158, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 44, "loc_deleted_sum": 0 }, { "ide": "neovim", "user_initiated_interaction_count": 0, "code_generation_activity_count": 18, "code_acceptance_activity_count": 6, "loc_suggested_to_add_sum": 18, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 7, "loc_deleted_sum": 0 } ], "totals_by_feature": [ { "feature": "code_completion", "user_initiated_interaction_count": 0, "code_generation_activity_count": 151, "code_acceptance_activity_count": 39, "loc_suggested_to_add_sum": 176, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 51, "loc_deleted_sum": 0 } ], "totals_by_language_feature": [ { "language": "tsx", "feature": "code_completion", "code_generation_activity_count": 122, "code_acceptance_activity_count": 29, "loc_suggested_to_add_sum": 144, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 34, "loc_deleted_sum": 0 }, { "language": "typescript", "feature": "code_completion", "code_generation_activity_count": 27, "code_acceptance_activity_count": 10, "loc_suggested_to_add_sum": 30, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 17, "loc_deleted_sum": 0 }, { "language": "zsh", "feature": "code_completion", "code_generation_activity_count": 2, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 2, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0 } ], "totals_by_language_model": [], "totals_by_model_feature": [], "loc_suggested_to_add_sum": 176, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 51, "loc_deleted_sum": 0, "pull_requests": { "total_reviewed": 118, "total_created": 129, "total_created_by_copilot": 0, "total_reviewed_by_copilot": 15, "total_merged": 95, "median_minutes_to_merge": 35.45, "total_suggestions": 37, "total_applied_suggestions": 2, "total_merged_created_by_copilot": 0, "total_copilot_suggestions": 30, "total_copilot_applied_suggestions": 0 }, "__enterprise": { "slug": "getport" } } ``` Sample Port entityJSON ``` { "identifier": "getport@2026-04-16", "title": "getport copilot-metrics 2026-04-16", "properties": { "record_date": "2026-04-16T00:00:00Z", "daily_active_users": 10, "code_generation_activity_count": 24, "code_acceptance_activity_count": 3, "loc_suggested_to_add_sum": 24, "loc_added_sum": 3, "user_initiated_interaction_count": 0, "weekly_active_users": 12, "monthly_active_agent_users": 1, "loc_suggested_to_delete_sum": 0, "loc_deleted_sum": 0, "prs_created_by_copilot": 0, "prs_reviewed_by_copilot": 8, "prs_merged_by_copilot": 0, "total_copilot_applied_suggestions": 0, "monthly_active_chat_users": 1, "acceptance_rate": 13 }, "relations": {}, "icon": "Github" } ``` BlueprintJSON Create in Port ``` { "identifier": "githubCopilotUserUsage", "title": "GitHub Copilot User Usage", "icon": "Github", "schema": { "properties": { "record_date": { "type": "string", "title": "Record Date", "format": "date-time" }, "user_initiated_interaction_count": { "type": "number", "title": "User Initiated Interaction Count" }, "code_generation_activity_count": { "type": "number", "title": "Code Generation Activity Count" }, "code_acceptance_activity_count": { "type": "number", "title": "Code Acceptance Activity Count" }, "loc_suggested_to_add_sum": { "type": "number", "title": "LOC Suggested To Add" }, "loc_suggested_to_delete_sum": { "type": "number", "title": "LOC Suggested To Delete" }, "loc_added_sum": { "type": "number", "title": "LOC Added" }, "loc_deleted_sum": { "type": "number", "title": "LOC Deleted" }, "git_hub_org": { "type": "string", "title": "GitHub Org" }, "username": { "type": "string", "title": "Username" }, "used_agent": { "type": "boolean", "title": "Used Agent" }, "used_chat": { "type": "boolean", "title": "Used Chat" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "acceptance_rate": { "title": "Acceptance Rate", "icon": "DefaultProperty", "calculation": "if (.properties.code_generation_activity_count == 0) then 0 else ((.properties.code_acceptance_activity_count / .properties.code_generation_activity_count) * 100 | round) end", "type": "number" } }, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: enterprise-user-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.user_login + "@" + .__enterprise.slug + "@" + .day) title: (.user_login + "@" + .__enterprise.slug + "@" + .day) blueprint: '"githubCopilotUserUsage"' properties: record_date: .day + "T00:00:00Z" code_generation_activity_count: .code_generation_activity_count code_acceptance_activity_count: .code_acceptance_activity_count loc_suggested_to_add_sum: .loc_suggested_to_add_sum loc_suggested_to_delete_sum: .loc_suggested_to_delete_sum loc_added_sum: .loc_added_sum loc_deleted_sum: .loc_deleted_sum user_initiated_interaction_count: .user_initiated_interaction_count git_hub_org: .__enterprise.slug used_agent: .used_agent used_chat: .used_chat username: .user_login relations: github_team: combinator: '"and"' rules: - property: '"member_logins"' operator: '"contains"' value: .user_login ``` Sample payloadJSON ``` { "report_start_day": "2026-04-02", "report_end_day": "2026-04-29", "day": "2026-04-15", "enterprise_id": "232528", "user_id": 253551706, "user_login": "jasonnwakaeze-debug", "user_initiated_interaction_count": 0, "code_generation_activity_count": 1, "code_acceptance_activity_count": 0, "totals_by_ide": [ { "ide": "intellij", "user_initiated_interaction_count": 0, "code_generation_activity_count": 1, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 3, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0, "last_known_plugin_version": { "sampled_at": "2026-04-15T17:29:55.273Z", "plugin": "copilot-intellij", "plugin_version": "1.5.66-243" }, "last_known_ide_version": { "sampled_at": "2026-04-15T17:29:55.273Z", "ide_version": "253.29346.142" } } ], "totals_by_feature": [ { "feature": "code_completion", "user_initiated_interaction_count": 0, "code_generation_activity_count": 1, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 3, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0 } ], "totals_by_language_feature": [ { "language": "json", "feature": "code_completion", "code_generation_activity_count": 1, "code_acceptance_activity_count": 0, "loc_suggested_to_add_sum": 3, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0 } ], "totals_by_language_model": [], "totals_by_model_feature": [], "used_agent": false, "used_chat": false, "loc_suggested_to_add_sum": 3, "loc_suggested_to_delete_sum": 0, "loc_added_sum": 0, "loc_deleted_sum": 0, "used_cli": false, "used_copilot_coding_agent": false, "used_copilot_cloud_agent": false, "used_copilot_code_review_active": false, "used_copilot_code_review_passive": true, "__enterprise": { "slug": "getport" } } ``` Sample Port entityJSON ``` { "identifier": "jasonnwakaeze-debug@getport@2026-03-08", "title": "jasonnwakaeze-debug@getport@2026-03-08", "properties": { "record_date": "2026-03-08T00:00:00Z", "code_generation_activity_count": 9, "code_acceptance_activity_count": 2, "loc_suggested_to_add_sum": 9, "loc_added_sum": 2, "user_initiated_interaction_count": 0, "git_hub_org": "getport", "loc_suggested_to_delete_sum": 0, "loc_deleted_sum": 0, "username": "jasonnwakaeze-debug", "used_agent": false, "used_chat": false, "acceptance_rate": 22 }, "relations": { "github_team": [] }, "icon": "Github" } ``` GitHub integration required The `github_team` relation on enterprise user usage metrics requires the [GitHub integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) to be installed and syncing `githubTeam` entities with the `member_logins` property populated. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Track AI credit cost[​](#track-ai-credit-cost "Direct link to Track AI credit cost") GitHub's Copilot usage metrics API includes an `ai_credits_used` field on user-level reports. It is the total AI credits a user consumed for that day, across all Copilot activity. The field is available on both organization and enterprise user reports. See GitHub's [changelog announcement](https://github.blog/changelog/2026-06-19-ai-credits-consumed-per-user-now-in-the-copilot-usage-metrics-api/). You can map that field into a dedicated blueprint to monitor spend, compare consumption across users, and estimate USD cost next to request volume. Metrics signal, not a billed total `ai_credits_used` is an overall per-user total. GitHub does not break it down by feature, model, or surface. Treat `amount` as a consumption estimate for analysis, not an invoice line. The mapping multiplies credits by `0.01` to store an estimated USD value. Add this mapping **in addition to** the default `organization-user-usage-metrics` (or `enterprise-user-usage-metrics`) block. Ocean can ingest the same kind twice into different blueprints. ### Create the cost usage blueprint 1. Go to the [data model](https://app.port.io/settings/data-model) page in Port. 2. Click **+ Blueprint**. 3. Click the `{...} Edit JSON` button. 4. Copy and paste the following JSON configuration: **GitHub Copilot cost usage blueprint (click to expand)** Create in Port ``` { "identifier": "githubCopilotCostUsage", "description": "Per-user USD cost across a date range", "title": "GitHub Copilot Cost Usage", "icon": "GithubCopilot", "schema": { "properties": { "record_date": { "type": "string", "format": "date-time", "title": "Range Start (UTC)" }, "range_end": { "type": "string", "format": "date-time", "title": "Range End (UTC)" }, "user_name": { "type": "string", "title": "User Name" }, "currency": { "type": "string", "title": "Currency" }, "amount": { "type": "number", "title": "AI Credit Amount" }, "requests": { "type": "number", "title": "Requests" } }, "required": [ "record_date" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` 5. Click **Save**. ### Add the mapping 1. Go to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Open your GitHub Copilot integration and select the **Mapping** tab. 3. Add one of the following resource blocks, then click **Save & Resync**. * Organization * Enterprise **Organization cost mapping (click to expand)** ``` - kind: organization-user-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.user_login + "@" + .__organization.login + "@" + .day) title: (.user_login + "@" + .__organization.login + "@" + .day) blueprint: '"githubCopilotCostUsage"' properties: record_date: .report_start_day + "T00:00:00Z" range_end: .report_end_day + "T00:00:00Z" user_name: .user_login currency: '"USD"' amount: (.ai_credits_used * 0.01) requests: .user_initiated_interaction_count ``` **Enterprise cost mapping (click to expand)** ``` - kind: enterprise-user-usage-metrics selector: query: 'true' port: entity: mappings: identifier: (.user_login + "@" + .__enterprise.slug + "@" + .day) title: (.user_login + "@" + .__enterprise.slug + "@" + .day) blueprint: '"githubCopilotCostUsage"' properties: record_date: .report_start_day + "T00:00:00Z" range_end: .report_end_day + "T00:00:00Z" user_name: .user_login currency: '"USD"' amount: (.ai_credits_used * 0.01) requests: .user_initiated_interaction_count ``` ## Migration Guide[​](#migration-guide "Direct link to Migration Guide") This guide covers the migration from the legacy `copilot-organization-metrics` kind to the new `organization-usage-metrics` kind. GitHub shut down the legacy Copilot Metrics API on **April 2, 2026**. ### What changed[​](#what-changed "Direct link to What changed") * The `copilot-organization-metrics` kind was replaced by `organization-usage-metrics`, which uses a new blueprint (`githubCopilotOrganizationUsage`) and features a restructured schema. * GitHub discontinued support for team-level metrics. As a result, the `copilot-team-metrics` kind stopped functioning after the sunset date. There is no replacement for this functionality in the new API or the integration. ### Migrate from copilot-organization-metrics[​](#migrate-from-copilot-organization-metrics "Direct link to Migrate from copilot-organization-metrics") **1. Add the new blueprint** Ensure you have the `githubCopilotOrganizationUsage` blueprint in Port. You can find the blueprint definition in the [Organization usage metrics](#organization-usage-metrics) section above. **2. Add the new integration mapping** In your Port connector configuration, add the `organization-usage-metrics` kind. You can find the full mapping in the [organization-usage-metrics](#organization-usage-metrics) resource section above. **3. Verify token permissions** Ensure you are using a classic PAT (fine-grained PATs are not supported) with at least one of the following scopes: `manage_billing:copilot` or `read:enterprise`. The token must be generated by an **Enterprise Administrator** or **Billing Manager**, or a user with the proper fine-grained permissions to access Copilot metrics. Also confirm that the **"Copilot usage metrics"** policy is set to **Enabled everywhere** in your enterprise settings under **AI Controls -> Copilot -> Metrics**. **4. Remove legacy kinds** Once data is flowing correctly via `organization-usage-metrics`, remove both `copilot-organization-metrics` and `copilot-team-metrics` from your mapping. Note that `copilot-team-metrics` has no replacement — it is simply being removed. Existing entities Entities already ingested under the `github_copilot_usage` blueprint will not be automatically deleted. A resync will auto-reconcile the environment. ## Related guide[​](#related-guide "Direct link to Related guide") [Visualize GitHub Copilot metrics](/guides/all/visualize-github-copilot-metrics/.md) --- # OpenAI metrics Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's OpenAI integration ingests foundational OpenAI usage metrics into your software catalog using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. It focuses on two reliable data sources: daily cost summaries and model-level usage statistics. The integration tracks usage across all OpenAI models, including ChatGPT models (GPT-4, GPT-3.5, etc.) and other OpenAI API models. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The OpenAI integration can ingest the following resources into Port: * `openai_daily_usage` – Daily totals for requests, tokens, and spend from `/dashboard/billing/usage`. * `openai_model_usage` – Model-level request and token breakdowns from `/usage`. * `openai_model` – Available OpenAI models and their details from `/models`. These resources provide visibility into your OpenAI usage, costs, and available models. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, you need: * An OpenAI API key with access to the usage and billing endpoints. * Network access from the Ocean integration to `api.openai.com`. **To create an OpenAI API key:** 1. Navigate to the [OpenAI Platform](https://platform.openai.com/) and sign in to your account. 2. Click on your profile icon in the top right corner and select **API keys**. 3. Click **Create new secret key**. 4. Give your key a name (e.g., "Port Integration") and click **Create secret key**. 5. Copy the API key immediately (it starts with `sk-`). You won't be able to see it again after closing the dialog. API key security Store your API key securely and never share it. The key provides access to your OpenAI account usage and billing data. Review the [OpenAI usage docs](https://platform.openai.com/docs/api-reference/usage) to understand the exact response structure returned by each endpoint. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. Configure: * **Identifier**: `openai-integration`. * **Base URL**: `https://api.openai.com/v1`. * **Authentication type**: `bearer_token`. * **Token**: your OpenAI API key (starts with `sk-`). 3. Click **Save & install**. Continue with the mappings After the integration is installed, continue to the **Configuration** section on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation 1. Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_OPENAI_API_KEY`. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-openai-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set integration.identifier="openai-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://api.openai.com/v1" \ --set integration.config.authType="bearer_token" \ --set integration.config.apiToken="YOUR_OPENAI_API_KEY" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters | Parameter | Description | Example | Required | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -------- | | `port.clientId` | Your Port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.baseUrl` | Your Port API URL (`https://api.port.io` for EU, `https://api.us.port.io` for US). | | ✅ | | `integration.config.baseUrl` | Base URL for the OpenAI API. | | ✅ | | `integration.config.authType` | Authentication type for OpenAI (use `bearer_token` for OpenAI). | bearer\_token | ✅ | | `integration.config.apiToken` | OpenAI API key (starts with `sk-`). | sk-abc123 | ✅ | | `integration.eventListener.type` | Event listener type for the integration. | POLLING | ✅ | | `integration.type` | Integration type. Must be `custom`. | custom | ✅ | | `integration.identifier` | Unique identifier for this integration instance. | openai-integration | ✅ | | `initializePortResources` | Create default blueprints and mappings on first run. | true | ❌ | | `scheduledResyncInterval` | Minutes between scheduled syncs. Defaults to event listener interval when omitted. | 120 | ❌ | | `sendRawDataExamples` | Send sample payloads for easier mapping. | true | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_OPENAI_API_KEY`. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://api.openai.com/v1" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="YOUR_OPENAI_API_KEY" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before syncing data, create the blueprints that define your OpenAI entities (usage metrics and available models). **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click the `+ Blueprint` button. 3. Copy each blueprint JSON from the sections below. **OpenAI daily usage blueprint (click to expand)** Create in Port ``` { "identifier": "openai_daily_usage", "title": "OpenAI Daily Usage", "icon": "OpenAI", "schema": { "properties": { "date": { "type": "string", "format": "date", "title": "Date" }, "total_requests": { "type": "number", "title": "Total Requests" }, "total_tokens": { "type": "number", "title": "Total Tokens" }, "total_cost": { "type": "number", "title": "Total Cost (USD)" } }, "required": [ "date" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **OpenAI model usage blueprint (click to expand)** Create in Port ``` { "identifier": "openai_model_usage", "title": "OpenAI Model Usage", "icon": "OpenAI", "schema": { "properties": { "model": { "type": "string", "title": "Model Name" }, "date": { "type": "string", "format": "date", "title": "Date" }, "requests": { "type": "number", "title": "Requests" }, "tokens": { "type": "number", "title": "Tokens Used" } }, "required": [ "model", "date" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **OpenAI Model blueprint (click to expand)** Create in Port ``` { "identifier": "openai_model", "title": "OpenAI Model", "icon": "Claude", "schema": { "properties": { "modelId": { "type": "string", "title": "Model ID" }, "object": { "type": "string", "title": "Object Type" }, "created": { "type": "number", "title": "Created Timestamp" }, "ownedBy": { "type": "string", "title": "Owned By" }, "permission": { "type": "array", "title": "Permissions" } }, "required": [ "modelId" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` 4. Click `Save` after each blueprint is added. ## Configuration[​](#configuration "Direct link to Configuration") Each resource maps an OpenAI endpoint to the Port entities defined above. **Key mapping components:** * **`kind`** – API endpoint path appended to `https://api.openai.com/v1`. * **`selector`** – Request payload, pagination controls, and data selection logic. * **`port.entity.mappings`** – JQ expressions that transform the API payload into Port entities. **Daily usage summary mapping (click to expand)** ``` resources: - kind: /dashboard/billing/usage selector: query: 'true' query_params: start_date: '((now | floor) - (86400 * 30)) | strftime("%Y-%m-%d")' end_date: '(now | floor) | strftime("%Y-%m-%d")' port: entity: mappings: identifier: "daily-" + (.timestamp // .aggregation_timestamp // "unknown") title: "OpenAI Usage " + (.timestamp // .aggregation_timestamp // "unknown") blueprint: '"openai_daily_usage"' properties: date: (.timestamp // .aggregation_timestamp // "" | split("T")[0]) total_requests: .total_requests // 0 total_tokens: .total_tokens // 0 total_cost: (.total_usage // 0) / 100 ``` Cost units `/dashboard/billing/usage` returns costs in cents. Divide by `100` to store USD. **Model usage breakdown mapping (click to expand)** ``` resources: - kind: /usage selector: query: 'true' query_params: date: '(now | floor) | strftime("%Y-%m-%d")' port: entity: mappings: identifier: .snapshot_id + "-" + ((.aggregation_timestamp // 0) | tostring) title: .snapshot_id + " usage" blueprint: '"openai_model_usage"' properties: model: .snapshot_id date: (.aggregation_timestamp // 0 | strftime("%Y-%m-%d")) requests: .n_requests // 0 tokens: (.n_context_tokens_total // 0) + (.n_generated_tokens_total // 0) ``` Snapshot identifiers `snapshot_id` typically corresponds to the model name (for example, `gpt-4o`). Use it for both the identifier and the model property to keep the mapping simple. **OpenAI Models mapping (click to expand)** ``` resources: - kind: /models selector: query: 'true' data_path: '.data' port: entity: mappings: identifier: .id title: .id blueprint: '"openai_model"' properties: modelId: .id object: .object ownedBy: .owned_by permission: .permission ``` Models endpoint The `/models` endpoint returns a list of all available OpenAI models. This is useful for cataloging which models are available in your account and tracking model availability over time. 5. Click `Save` to persist the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to expand beyond the starter resources, use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md) to: 1. Test additional OpenAI endpoints. 2. Explore the response shape and detected property types. 3. Generate blueprint JSON and mapping snippets automatically. 4. Export installation commands with your configuration pre-filled. Start with the daily and model usage entities above, then add more resources (such as per-organization or per-team reports) once you verify the value. --- # APM & alerting Port's APM & alerting integrations make it easy to keep your software catalog up to date based on information stored in your preferred APM & alerting tool. For more information, check out the documentation for your respective tool: ## [🗃Datadog](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/.md) ## [📄️Dynatrace](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/dynatrace/.md) [Sync Dynatrace monitoring data, problems, and services into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/dynatrace/.md) ## [📄️New Relic](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/newrelic/.md) [Sync New Relic monitoring data, alerts, and services into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/newrelic/.md) ## [📄️Sentry](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/sentry/.md) [Sync Sentry projects, issues, and error tracking data into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/sentry/.md) ## [📄️Azure Monitor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/azure-monitor/.md) [Learn how to ingest Azure Monitor alerts, activity logs, and metrics into Port using the no-code Ocean custom integration framework.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/azure-monitor/.md) ## [📄️Prometheus](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/prometheus/.md) [Learn how to ingest Prometheus alerts, rules, and scrape targets into Port using the no-code Ocean custom integration framework.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/prometheus/.md) --- # Azure Monitor Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's Azure Monitor integration allows you to ingest Azure Monitor resources into your software catalog using the [No-code Ocean custom integration](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview) framework. After installing this integration, you can visualize metrics, alerts, activity logs, and diagnostic settings from your Azure resources. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The Azure Monitor integration can ingest the following resources into Port. It is possible to reference any field that appears in the API responses in the mapping configuration. For detailed API documentation, see the [Azure Monitor REST API documentation](https://learn.microsoft.com/en-us/rest/api/monitor/). * **Metric Alerts** - Alert rules from [`/subscriptions/{subscriptionId}/providers/Microsoft.Insights/metricAlerts`](https://learn.microsoft.com/en-us/rest/api/monitor/metricalerts/listbysubscription). * **Activity Logs** - Activity log events from [`/subscriptions/{subscriptionId}/providers/Microsoft.Insights/eventtypes/management/values`](https://learn.microsoft.com/en-us/rest/api/monitor/activity-logs/list). * **Diagnostic Settings** - Diagnostic settings from resource-specific endpoints (varies by resource type). * **Metrics** - Resource metrics from resource-specific endpoints (requires resource-specific paths). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, the following prerequisites are required: * An Azure subscription with appropriate permissions. * A Microsoft Entra ID application registration with appropriate API permissions. * A client secret for the application. * Admin consent for the required permissions. * An access token (bearer token) for Azure Resource Manager API. **Register an application in Microsoft Entra ID** 1. Log in to the [Azure Portal](https://portal.azure.com). 2. Navigate to **Microsoft Entra ID** > **App registrations**. 3. Click **New registration**. 4. Provide a name for your application (e.g., "Port Azure Monitor Integration"). 5. Select **Accounts in this organizational directory only**. 6. Click **Register**. 7. Note the **Application (client) ID** and **Directory (tenant) ID**. **Create a client secret** 1. In your app registration, navigate to **Certificates & secrets**. 2. Click **New client secret**. 3. Provide a description (e.g., "Port Azure Monitor Secret"). 4. Select an expiration period. 5. Click **Add**. 6. **Copy the secret value immediately** - it will not be shown again. Secret security Store your client secret securely and never share it. The secret provides access to your Azure resources. **Grant API permissions:** 1. In your app registration, navigate to **API permissions**. 2. Click **Add a permission**. 3. Select **Azure Service Management**. 4. Select **Application permissions**. 5. Add the following permission: * `user_impersonation` - Access Azure Service Management as organization users. 6. Click **Add permissions**. 7. Click **Grant admin consent for \[Your Organization]**. Admin consent required You need an Entra ID administrator to grant consent for these permissions before the integration can access Azure Monitor data. **Generate an access token (bearer token)** To authenticate with Azure Resource Manager API we need to obtain an OAuth2 access token. We can use the following curl command to get a token: ``` curl -X POST "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "scope=https://management.azure.com/.default" \ -d "grant_type=client_credentials" | jq -r '.access_token' ``` Replace: * `YOUR_TENANT_ID` with your Directory (tenant) ID. * `YOUR_CLIENT_ID` with your Application (client) ID. * `YOUR_CLIENT_SECRET` with your client secret value. Token expiration Access tokens typically expire after 1 hour. For production use, consider implementing automatic token refresh or using OAuth2 client credentials flow with automatic token management. **To find your subscription ID:** 1. Log in to the [Azure Portal](https://portal.azure.com). 2. Navigate to **Subscriptions**. 3. Copy the **Subscription ID** you want to monitor. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. Configure: * **Identifier**: `azure-monitor-integration`. * **Base URL**: `https://management.azure.com`. * **Authentication type**: `bearer_token`. * **Token**: your Azure Resource Manager bearer token (see the Prerequisites section for how to generate it). 3. Click **Save & install**. Continue with the mappings After the integration is installed, continue to the **Configuration** section on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_BEARER_TOKEN`. Get your bearer token using the curl command in the Prerequisites section. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-azure-monitor-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set scheduledResyncInterval=120 \ --set integration.identifier="azure-monitor-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://management.azure.com" \ --set integration.config.authType="bearer_token" \ --set integration.config.apiToken="YOUR_BEARER_TOKEN" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters This table summarizes the available parameters for the installation. | Parameter | Description | Example | Required | | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -------- | | `port.clientId` | Your Port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.baseUrl` | Your Port API URL (`https://api.port.io` for EU, `https://api.us.port.io` for US). | | ✅ | | `integration.config.baseUrl` | Base URL of the Azure Resource Manager API. | | ✅ | | `integration.config.authType` | Authentication type for the API (use `bearer_token` for Azure Monitor). | bearer\_token | ✅ | | `integration.config.apiToken` | Azure Resource Manager API bearer token created via the OAuth2 client credentials flow. | eyJ0eXAiOiJKV1QiLCJub25jZSI6... | ✅ | | `integration.config.paginationType` | How the API handles pagination (offset, page, cursor, or none). | none | ❌ | | `integration.eventListener.type` | Event listener type. See [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md). | POLLING | ✅ | | `integration.type` | Integration type (must be `custom`). | custom | ✅ | | `integration.identifier` | Unique identifier for the integration instance. | azure-monitor-integration | ✅ | | `scheduledResyncInterval` | Minutes between scheduled syncs. When omitted, the event listener interval is used. | 120 | ❌ | | `initializePortResources` | When true, creates default blueprints and mappings on first run. | true | ❌ | | `sendRawDataExamples` | Sends sample payloads from the API to Port for easier mapping. | true | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_BEARER_TOKEN`. Get your bearer token using the curl command in the Prerequisites section. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://management.azure.com" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="YOUR_BEARER_TOKEN" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before the integration can sync data, you need to create the required blueprints in Port. These blueprints define the data model for your Azure Monitor resources. **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on the `+ Blueprint` button. 3. Copy and paste each blueprint JSON from the sections below. **Azure monitor alert blueprint (click to expand)** Metric alert rules: Create in Port ``` { "identifier": "azure-monitor-alert", "title": "Azure Monitor Alert", "icon": "Microsoft", "schema": { "properties": { "name": { "title": "Name", "type": "string" }, "description": { "title": "Description", "type": "string" }, "severity": { "title": "Severity", "type": "string" }, "enabled": { "title": "Enabled", "type": "boolean" }, "evaluationFrequency": { "title": "Evaluation Frequency", "type": "string" }, "windowSize": { "title": "Window Size", "type": "string" }, "targetResourceType": { "title": "Target Resource Type", "type": "string" }, "targetResourceRegion": { "title": "Target Resource Region", "type": "string" }, "subscriptionId": { "title": "Subscription ID", "type": "string" }, "resourceGroup": { "title": "Resource Group", "type": "string" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Azure monitor activity log blueprint (click to expand)** Activity log events: Create in Port ``` { "identifier": "azure-monitor-activity-log", "title": "Azure Monitor Activity Log", "icon": "Microsoft", "schema": { "properties": { "eventName": { "title": "Event Name", "type": "string" }, "resourceId": { "title": "Resource ID", "type": "string" }, "resourceGroupName": { "title": "Resource Group Name", "type": "string" }, "resourceProviderName": { "title": "Resource Provider Name", "type": "string" }, "status": { "title": "Status", "type": "string" }, "subStatus": { "title": "Sub Status", "type": "string" }, "eventTimestamp": { "title": "Event Timestamp", "type": "string", "format": "date-time" }, "caller": { "title": "Caller", "type": "string" }, "operationName": { "title": "Operation Name", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` ## Configuration[​](#configuration "Direct link to Configuration") After installation, define which endpoints to sync in your integration configuration. Each resource maps an API endpoint to Port entities using [JQ expressions](https://stedolan.github.io/jq/manual/) to transform the data. **Key mapping components:** * **`kind`**: The API endpoint path (combined with your base URL). * **`selector.query`**: JQ filter to include/exclude entities (use `'true'` to sync all). * **`selector.data_path`**: JQ expression pointing to the array of items in the response. * **`port.entity.mappings`**: How to map API fields to Port entity properties. For more details on how the no-code Ocean custom integration works, see the [How it works](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview#how-it-works) section in the custom integration overview. **Azure Monitor API response format:** Azure Monitor API responses typically follow this structure: ``` { "value": [ { "id": "/subscriptions/.../providers/Microsoft.Insights/metricAlerts/alert-name", "name": "alert-name", "type": "Microsoft.Insights/metricAlerts", "properties": { "description": "Alert description", "severity": 2, "enabled": true, ... } } ] } ``` The actual data array is in the `.value` property. **To configure the mappings:** 1. Go to your [connectors page](https://app.port.io/settings/data-sources). 2. Find your Azure Monitor integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below. **Metric Alerts mapping (click to expand)** ``` resources: - kind: /subscriptions/YOUR_SUBSCRIPTION_ID/providers/Microsoft.Insights/metricAlerts selector: query: 'true' data_path: .value query_params: api-version: '2018-03-01' port: entity: mappings: identifier: .id title: .name blueprint: '"azure-monitor-alert"' properties: name: .name description: .properties.description severity: .properties.severity enabled: .properties.enabled evaluationFrequency: .properties.evaluationFrequency windowSize: .properties.windowSize targetResourceType: .properties.targetResourceType targetResourceRegion: .properties.targetResourceRegion subscriptionId: (.id | split("/")[2]) resourceGroup: (.properties.scopes[0] | split("/")[4] // "") createdAt: .properties.createdAt updatedAt: .properties.updatedAt ``` Subscription ID Replace `YOUR_SUBSCRIPTION_ID` in the `kind` path with your actual Azure subscription ID. **Activity Logs mapping (click to expand)** ``` resources: - kind: /subscriptions/YOUR_SUBSCRIPTION_ID/providers/Microsoft.Insights/eventtypes/management/values selector: query: 'true' data_path: .value query_params: api-version: '2015-04-01' $filter: "eventTimestamp ge '2025-12-01T00:00:00Z'" port: entity: mappings: identifier: .id title: .eventName.localizedValue // .operationName.localizedValue blueprint: '"azure-monitor-activity-log"' properties: eventName: .eventName.localizedValue resourceId: .resourceId resourceGroupName: .resourceGroupName resourceProviderName: .resourceProviderName.value status: .status.localizedValue subStatus: .subStatus.localizedValue eventTimestamp: .eventTimestamp caller: .caller operationName: .operationName.localizedValue ``` Activity log filtering Activity logs can be very large and the API requires a date filter. Use the `$filter` query parameter to limit results by date range. The start time cannot be more than 90 days in the past. Replace the date in the filter with a date within the last 90 days. 5. Click `Save` to save the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to customize your setup or test different API endpoints before committing to a configuration, use the [interactive builder](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration). **The interactive builder helps you:** 1. Test your Azure Monitor API endpoints with live data. 2. Automatically detect the data structure and field types. 3. Generate blueprints and resource mappings tailored to your preferences. 4. Get installation commands with your configuration pre-filled. Simply provide your Azure Monitor API details, and the builder will generate everything you need to install and create the integration in Port. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Authentication errors[​](#authentication-errors "Direct link to Authentication errors") **Symptom:** 401 "InvalidAuthenticationToken" or "Lifetime validation failed, the token is expired" errors. **Solution:** * Access tokens expire after 1 hour * For production use, implement automatic token refresh or use OAuth2 client credentials flow with automatic token management * See the [prerequisites](#prerequisites) section for token generation commands. ### API version errors[​](#api-version-errors "Direct link to API version errors") **Symptom:** 400 "Bad Request" errors with API version messages. **Solution:** * Azure Monitor API requires the `api-version` query parameter. * Ensure you include `api-version` in your `query_params` (e.g., `api-version: "2018-03-01"`). * Check the [Azure Monitor REST API documentation](https://learn.microsoft.com/en-us/rest/api/monitor/) for the correct API version for your endpoint. ### Subscription access errors[​](#subscription-access-errors "Direct link to Subscription access errors") **Symptom:** 403 "Forbidden" or "Authorization failed" errors. **Solution:** * Ensure your application has the correct permissions in Azure. * Verify the subscription ID is correct. * Check that admin consent has been granted for the API permissions. * Verify your application has "Reader" role or appropriate permissions on the subscription. ### Activity log size issues[​](#activity-log-size-issues "Direct link to Activity log size issues") **Symptom:** Very large responses or timeouts when fetching activity logs. **Solution:** * Use `$filter` query parameter to limit results by date range. * Consider syncing only recent activity logs (e.g., last 7 days). * Use pagination if available for the specific endpoint. * Adjust `scheduledResyncInterval` to sync less frequently. --- # Datadog ![Datadog icon](/img/guides/icons/Datadog.svg)![Datadog icon](/img/guides/icons/dark/Datadog.svg) Loading version... Port's Datadog integration allows you to model Datadog resources in Port and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Monitor](https://docs.datadoghq.com/api/latest/monitors/#get-all-monitor-details)[Service](https://docs.datadoghq.com/api/latest/service-definition/#get-all-service-definitions)[SLO](https://docs.datadoghq.com/api/latest/service-level-objectives/#get-all-slos)[SLO History](https://docs.datadoghq.com/api/latest/service-level-objectives/#get-an-slos-history)[Service Metric](https://docs.datadoghq.com/api/latest/metrics/#query-timeseries-points)[User](https://docs.datadoghq.com/api/latest/users/#list-all-users)[Role](https://docs.datadoghq.com/api/latest/roles/#list-roles)[Team](https://docs.datadoghq.com/api/latest/teams/#get-all-teams)[Service Dependency](https://docs.datadoghq.com/api/latest/service-dependencies/#get-all-service-dependencies)[Organization](https://docs.datadoghq.com/api/latest/organizations/list-your-managed-organizations/)[Host](https://docs.datadoghq.com/api/latest/hosts/#get-all-hosts-for-your-organization) ## Core support [Live eventsSee supported resources](#set-up-live-events) **SLO History**, **Service Metric** and **Service Dependency** resources are not collected out of the box.
Follow the examples [here](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/examples) to configure blueprints and resource mappings. ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: monitor selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"datadogMonitor"' properties: tags: .tags monitorType: .type overallState: .overall_state thresholds: .thresholds priority: .priority createdBy: .creator.email createdAt: .created updatedAt: .modified link: (\"https://app.datadoghq.com/monitors/\" + (.id | tostring)) relations: owner_team_datadog: .tags | map(select(startswith("team:"))) | unique | map(split(":")[1]) service_datadog: .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) service: combinator: '"and"' rules: - property: '"datadog_service_id"' operator: '"in"' value: .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) - kind: service selector: query: "true" port: entity: mappings: identifier: .attributes.schema.\"dd-service\" title: .attributes.schema.\"dd-service\" blueprint: '"datadogService"' properties: application: .attributes.schema.application languages: .attributes.schema.languages description: .attributes.schema.description tags: .attributes.schema.tags type: .attributes.schema.type links: (\"https://app.datadoghq.com/services?selectedService=\" + (.attributes.schema.\"dd-service\")) owners: '[.attributes.schema.contacts[]? | select(.type == "email") | .contact]' relations: owner_team_datadog: .team_it_doesnt_work_yet - kind: slo selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"datadogSlo"' properties: tags: .tags sloType: .type description: .description warningThreshold: .warning_threshold targetThreshold: .target_threshold createdBy: .creator.email createdAt: .created_at | todate updatedAt: .modified_at | todate link: (\"https://app.datadoghq.com/slo/manage?sp=\" + (\"%5B%7B%22p%22%3A%7B%22id%22%3A%22\" + (.id | tostring) + \"%22%7D%2C%22i%22%3A%22slo-panel%22%7D%5D\")) relations: monitors: .monitor_ids | map(tostring) owner_team_datadog: .monitor_tags + .tags | map(select(startswith("team:"))) | unique | map(split(":")[1]) service_datadog: .monitor_tags + .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) service: combinator: '"and"' rules: - property: '"datadog_service_id"' operator: '"in"' value: .monitor_tags + .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) - kind: host selector: query: '[.sources[] | . as $source | ["azure", "gcp", "gce", "aws"] | contains([$source])] | any(.)' port: entity: mappings: identifier: .id | tostring title: .aws_name // .host_name blueprint: '"datadogCloudResource"' properties: up: .up host_name: .host_name platform: .meta.platform is_muted: .is_muted machine: .meta.machine description: .description sources: .sources cpu_cores: .meta.cpuCores agent_version: .meta.agent_version tags: .tags_by_source - kind: host selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .aws_name // .host_name blueprint: '"datadogHost"' properties: up: .up host_name: .host_name platform: .meta.platform is_muted: .is_muted machine: .meta.machine description: .description sources: .sources cpu_cores: .meta.cpuCores agent_version: .meta.agent_version tags: .tags_by_source - kind: user selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogUser"' properties: email: .attributes.email handle: .attributes.handle status: .attributes.status disabled: .attributes.disabled verified: .attributes.verified createdAt: .attributes.created_at | todate - kind: team selector: query: "true" port: entity: mappings: identifier: .id title: .id blueprint: '"datadogTeam"' - kind: team selector: query: "true" includeMembers: "true" port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogTeam"' properties: description: .attributes.description handle: .attributes.handle userCount: .attributes.user_count summary: .attributes.summary createdAt: .attributes.created_at | todate relations: members: if .__members then .__members[].id else [] end - kind: role selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogRole"' properties: createdAt: .attributes.created_at | todate modifiedAt: .attributes.modified_at | todate custom: if .attributes.managed == true then false else true end userCount: .attributes.user_count ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/examples/.md). MonitorServiceSLOSLO HistoryService MetricUserRoleTeamService DependencyOrganization Default mappingYAML ``` - kind: monitor selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"datadogMonitor"' properties: tags: .tags monitorType: .type overallState: .overall_state thresholds: .thresholds priority: .priority createdBy: .creator.email createdAt: .created updatedAt: .modified link: (\"https://app.datadoghq.com/monitors/\" + (.id | tostring)) relations: owner_team_datadog: .tags | map(select(startswith("team:"))) | unique | map(split(":")[1]) service_datadog: .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) service: combinator: '"and"' rules: - property: '"datadog_service_id"' operator: '"in"' value: .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) ``` Sample payloadJSON ``` { "id":15173866, "org_id":1000147697, "type":"query alert", "name":"A change @webhook-PORT", "message":"A change has happened", "tags":[ "app:webserver" ], "query":"change(avg(last_5m),last_1h):avg:datadog.agent.running{local} by {version,host} > 40", "options":{ "thresholds":{ "critical":40.0, "warning":30.0 }, "notify_audit":false, "include_tags":true, "new_group_delay":60, "notify_no_data":false, "timeout_h":0, "silenced":{ } }, "multi":true, "created_at":1706707941000, "created":"2024-01-31T13:32:21.270116+00:00", "modified":"2024-02-02T16:31:40.516062+00:00", "deleted": null, "restricted_roles": [ "83659eba-5ce6-11f1-b7ce-da7ad0900002", "dbd83320-396e-11ef-bf24-da7ad0900002" ], "priority":5, "overall_state_modified":"2024-03-08T20:52:46+00:00", "overall_state":"No Data", "creator":{ "name":"John Doe", "email":"john.doe@gmail.com", "handle":"john.doe@gmail.com", "id":1001199545 }, "matching_downtimes":[ ] } ``` Port entityJSON ``` { "identifier": "15173866", "title": "A change @webhook-PORT", "icon": "Datadog", "blueprint": "datadogMonitor", "team": [], "properties": { "tags": [ "app:webserver" ], "overallState": "No Data", "priority": "5", "createdAt": "2024-01-31T13:32:21.270116+00:00", "updatedAt": "2024-02-02T16:31:40.516062+00:00", "createdBy": "john.doe@gmail.com", "monitorType": "query alert" }, "relations": {}, "createdAt": "2024-05-29T09:43:34.750Z", "createdBy": "", "updatedAt": "2024-05-29T09:43:34.750Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: service selector: query: 'true' port: entity: mappings: identifier: .attributes.schema.\"dd-service\" title: .attributes.schema.\"dd-service\" blueprint: '"datadogService"' properties: application: .attributes.schema.application languages: .attributes.schema.languages description: .attributes.schema.description tags: .attributes.schema.tags type: .attributes.schema.type links: (\"https://app.datadoghq.com/services?selectedService=\" + (.attributes.schema.\"dd-service\")) owners: '[.attributes.schema.contacts[]? | select(.type == "email") | .contact]' relations: owner_team_datadog: .team_it_doesnt_work_yet ``` Sample payloadJSON ``` { "type":"service-definition", "id":"04fbab48-a233-4592-8c53-d1bfe282e6c3", "attributes":{ "meta":{ "last-modified-time":"2024-05-29T10:31:06.833444245Z", "github-html-url":"", "ingestion-source":"api", "origin":"unknown", "origin-detail":"", "warnings":[ { "keyword-location":"/properties/integrations/properties/opsgenie/properties/service-url/pattern", "instance-location":"/integrations/opsgenie/service-url", "message":"does not match pattern '^(https?://)?[a-zA-Z\\\\d_\\\\-.]+\\\\.opsgenie\\\\.com/service/([a-zA-Z\\\\d_\\\\-]+)/?$'" }, { "keyword-location":"/properties/integrations/properties/pagerduty/properties/service-url/pattern", "instance-location":"/integrations/pagerduty/service-url", "message":"does not match pattern '^(https?://)?[a-zA-Z\\\\d_\\\\-.]+\\\\.pagerduty\\\\.com/service-directory/(P[a-zA-Z\\\\d_\\\\-]+)/?$'" } ], "ingested-schema-version":"v2.1" }, "schema":{ "schema-version":"v2.2", "dd-service":"inventory-management", "team":"Inventory Management Team", "application":"Inventory System", "tier":"Tier 1", "description":"Service for managing product inventory and stock levels.", "lifecycle":"production", "contacts":[ { "name":"Inventory Team", "type":"email", "contact":"inventory-team@example.com" }, { "name":"Warehouse Support", "type":"email", "contact":"warehouse-support@example.com" } ], "links":[ { "name":"Repository", "type":"repo", "provider":"GitHub", "url":"https://github.com/example/inventory-service" }, { "name":"Runbook", "type":"runbook", "provider":"Confluence", "url":"https://wiki.example.com/runbooks/inventory-service" } ], "tags":[ "inventory", "stock" ], "integrations":{ "pagerduty":{ "service-url":"https://pagerduty.com/services/inventory" }, "opsgenie":{ "service-url":"https://opsgenie.com/services/inventory", "region":"US" } }, "extensions":{ "qui_6":{ } } } } } } ``` Port entityJSON ``` { "identifier": "inventory-management", "title": "inventory-management", "icon": "Datadog", "blueprint": "datadogService", "team": [], "properties": { "owners": [ "inventory-team@example.com", "warehouse-support@example.com" ], "links": [ "https://github.com/example/inventory-service", "https://wiki.example.com/runbooks/inventory-service" ], "description": "Service for managing product inventory and stock levels.", "tags": [ "inventory", "stock" ], "application": "Inventory System" }, "relations": {}, "createdAt": "2024-05-29T10:31:44.283Z", "createdBy": "", "updatedAt": "2024-05-29T10:31:44.283Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: slo selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"datadogSlo"' properties: tags: .tags sloType: .type description: .description warningThreshold: .warning_threshold targetThreshold: .target_threshold createdBy: .creator.email createdAt: .created_at | todate updatedAt: .modified_at | todate link: (\"https://app.datadoghq.com/slo/manage?sp=\" + (\"%5B%7B%22p%22%3A%7B%22id%22%3A%22\" + (.id | tostring) + \"%22%7D%2C%22i%22%3A%22slo-panel%22%7D%5D\")) relations: monitors: .monitor_ids | map(tostring) owner_team_datadog: .monitor_tags + .tags | map(select(startswith("team:"))) | unique | map(split(":")[1]) service_datadog: .monitor_tags + .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) service: combinator: '"and"' rules: - property: '"datadog_service_id"' operator: '"in"' value: .monitor_tags + .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) ``` Sample payloadJSON ``` { "id":"b6869ae6189d59baa421feb8b437fe9e", "name":"Availability SLO for shopping-cart service", "tags":[ "service:shopping-cart", "env:none" ], "monitor_tags":[ ], "thresholds":[ { "timeframe":"7d", "target":99.9, "target_display":"99.9" } ], "type":"monitor", "type_id":0, "description":"This SLO tracks the availability of the shopping-cart service. Availability is measured as the number of successful requests divided by the number of total requests for the service", "timeframe":"7d", "target_threshold":99.9, "monitor_ids":[ 15173866, 15216083, 15254771 ], "creator":{ "name":"John Doe", "handle":"john.doe@gmail.com", "email":"john.doe@gmail.com" }, "created_at":1707215619, "modified_at":1707215619 } ``` Port entityJSON ``` { "identifier": "b6869ae6189d59baa421feb8b437fe9e", "title": "Availability SLO for shopping-cart service", "icon": "Datadog", "blueprint": "datadogSlo", "team": [], "properties": { "description": "This SLO tracks the availability of the shopping-cart service. Availability is measured as the number of successful requests divided by the number of total requests for the service", "updatedAt": "2024-02-06T10:33:39Z", "createdBy": "ahosea15@gmail.com", "sloType": "monitor", "targetThreshold": "99.9", "tags": [ "service:shopping-cart", "env:none" ], "createdAt": "2024-02-06T10:33:39Z" }, "relations": { "monitors": [ "15173866", "15216083", "15254771" ], "services": [ "shopping-cart" ] }, "createdAt": "2024-05-29T09:43:51.946Z", "createdBy": "", "updatedAt": "2024-05-29T12:02:01.559Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: sloHistory selector: query: "true" timeframe: 7 periodOfTimeInMonths: 6 port: entity: mappings: identifier: .slo.id | tostring title: .slo.name blueprint: '"datadogSloHistory"' properties: monitory_type: .type sampling_start_date: .from_ts | todate sampling_end_date: .to_ts | todate sliValue: .overall.sli_value relations: slo: .slo.id ``` Sample payloadJSON ``` { "thresholds": { "7d": { "timeframe": "7d", "target": 99, "target_display": "99." } }, "from_ts": 1719254776, "to_ts": 1719859576, "type": "monitor", "type_id": 0, "slo": { "id": "5ec82408e83c54b4b5b2574ee428a26c", "name": "Host {{host.name}} with IP {{host.ip}} is not having enough memory", "tags": [ "p69hx03", "pages-laptop" ], "monitor_tags": [], "thresholds": [ { "timeframe": "7d", "target": 99, "target_display": "99." } ], "type": "monitor", "type_id": 0, "description": "Testing SLOs from DataDog", "timeframe": "7d", "target_threshold": 99, "monitor_ids": [ 147793 ], "creator": { "name": "John Doe", "handle": "janesmith@gmail.com", "email": "janesmith@gmail.com" }, "created_at": 1683878238, "modified_at": 1684773765 }, "overall": { "name": "Host {{host.name}} with IP {{host.ip}} is not having enough memory", "preview": false, "monitor_type": "query alert", "monitor_modified": 1683815332, "errors": null, "span_precision": 2, "history": [ [ 1714596313, 1 ] ], "uptime": 3, "sli_value": 10, "precision": { "custom": 2, "7d": 2 }, "corrections": [], "state": "breached" } } ``` Port entityJSON ``` { "identifier": "5ec82408e83c54b4b5b2574ee428a26c", "title": "Host {{host.name}} with IP {{host.ip}} is not having enough memory", "icon": "Datadog", "blueprint": "datadogSloHistory", "team": [], "properties": { "sampling_end_date": "2024-07-01T18:46:16Z", "sliValue": 10, "sampling_start_date": "2024-06-24T18:46:16Z" }, "relations": { "slo": "5ec82408e83c54b4b5b2574ee428a26c" }, "createdAt": "2024-07-01T09:43:51.946Z", "createdBy": "", "updatedAt": "2024-07-01T12:02:01.559Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: serviceMetric selector: query: "true" datadogSelector: metric: "avg:system.mem.used" env: tag: env value: "*" service: tag: servicename value: "*" timeframe: 10 port: entity: mappings: blueprint: '"datadogServiceMetric"' identifier: .__query_id title: .query properties: query: .__query series: .series res_type: .res_type from_date: ".from_date / 1000 | todate" to_date: ".to_date / 1000 | todate" env: .__env relations: service: .__service - kind: serviceMetric selector: query: "true" datadogSelector: metric: "avg:system.disk.used" env: tag: env value: "prod" service: tag: servicename value: "*" timeframe: 5 port: entity: mappings: blueprint: '"datadogServiceMetric"' identifier: .__query_id title: .query properties: query: .__query series: .series res_type: .res_type from_date: ".from_date / 1000 | todate" to_date: ".to_date / 1000 | todate" env: .__env relations: service: .__service ``` Sample payloadJSON Response Enrichment The Datadog response is enriched with a variety of metadata fields, including: * `__service`: The name or identifier of the service generating the data. * `__query_id`: A unique identifier for the query that generated the data. * `__query`: The original query used to retrieve the data. * `__env`: The environment associated with the data (e.g., production, staging). This enrichment significantly enhances the usability of the Datadog response by providing valuable context and facilitating easier analysis and troubleshooting. ``` { "status": "ok", "res_type": "time_series", "resp_version": 1, "query": "avg:system.mem.used{service:inventory-management,env:staging}", "from_date": 1723796537000, "to_date": 1723797137000, "series": [ { "unit": [ { "family": "bytes", "id": 2, "name": "byte", "short_name": "B", "plural": "bytes", "scale_factor": 1.0 } ], "query_index": 0, "aggr": "avg", "metric": "system.mem.used", "tag_set": [], "expression": "avg:system.mem.used{env:staging,service:inventory-management}", "scope": "env:staging,service:inventory-management", "interval": 2, "length": 39, "start": 1723796546000, "end": 1723797117000, "pointlist": [ [1723796546000.0, 528986112.0], [1723796562000.0, 531886080.0], [1723796576000.0, 528867328.0], [1723796592000.0, 522272768.0], [1723796606000.0, 533704704.0], [1723796846000.0, 533028864.0], [1723796862000.0, 527417344.0], [1723796876000.0, 531513344.0], [1723796892000.0, 533577728.0], [1723796906000.0, 533471232.0], [1723796922000.0, 528125952.0], [1723796936000.0, 530542592.0], [1723796952000.0, 530767872.0], [1723796966000.0, 526966784.0], [1723796982000.0, 528560128.0], [1723796996000.0, 530792448.0], [1723797012000.0, 527384576.0], [1723797026000.0, 529534976.0], [1723797042000.0, 521650176.0], [1723797056000.0, 531001344.0], [1723797072000.0, 525955072.0], [1723797086000.0, 529469440.0], [1723797102000.0, 532279296.0], [1723797116000.0, 526979072.0] ], "display_name": "system.mem.used", "attributes": {} } ], "values": [], "times": [], "message": "", "group_by": [], "__service": "inventory-management", "__query_id": "avg:system.mem.used/service:inventory-management/env:staging", "__query": "avg:system.mem.used", "__env": "staging" } ``` Port entityJSON ``` { "identifier": "avg:system.disk.used/service:inventory-management/env:prod", "title": "avg:system.disk.used{service:inventory-management,env:prod}", "icon": "Datadog", "blueprint": "datadogServiceMetric", "team": [], "properties": { "query": "avg:system.disk.used", "series": [], "res_type": "time_series", "from_date": "2024-08-16T07:32:00Z", "to_date": "2024-08-16T08:02:00Z", "env": "prod" }, "relations": { "service": "inventory-management" }, "createdAt": "2024-08-15T15:54:36.638Z", "createdBy": "", "updatedAt": "2024-08-16T08:02:02.399Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogUser"' properties: email: .attributes.email handle: .attributes.handle status: .attributes.status disabled: .attributes.disabled verified: .attributes.verified createdAt: .attributes.created_at | todate ``` Sample payloadJSON ``` { "type": "users", "id": "468a7e28-4d70-11ef-9b68-c2d109cdf094", "attributes": { "name": "", "handle": "john.doe@example.com", "created_at": "2024-07-29T06:03:28.693227+00:00", "modified_at": "2024-07-29T06:05:30.392353+00:00", "email": "john.doe@example.com", "icon": "", "title": "", "verified": true, "service_account": false, "disabled": false, "allowed_login_methods": [], "status": "Active", "mfa_enabled": false }, "relationships": { "roles": { "data": [ { "type": "roles", "id": "dbc3577a-396e-11ef-b12b-da7ad0900002" } ] }, "org": { "data": { "type": "orgs", "id": "dbb758e9-396e-11ef-800c-bea43249b5f6" } } } } ``` Port entityJSON ``` { "identifier": "1e675d18-b002-11ef-9df4-2a2722803530", "title": "Matan Grady", "team": [], "properties": { "email": "john.doe@example.com", "handle": "john.doe@example.com", "status": "Active", "disabled": false, "verified": true }, "relations": { "team": [] }, "icon": "Datadog" } ``` Default mappingYAML ``` - kind: role selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogRole"' properties: createdAt: .attributes.created_at | todate modifiedAt: .attributes.modified_at | todate custom: if .attributes.managed == true then false else true end userCount: .attributes.user_count ``` Sample payloadJSON ``` { "id": "dbc3577a-396e-11ef-b12b-da7ad0900002", "type": "roles", "attributes": { "created_at": "2024-07-03T19:02:56.796169Z", "managed": true, "modified_at": "2024-07-03T19:02:56.797067Z", "name": "Datadog Admin Role", "team_count": 0, "user_count": 59 }, "relationships": { "permissions": { "data": [ { "id": "0137c052-2957-11f1-a9fb-da7ad0900002", "type": "permissions" } ] } } } ``` Port entityJSON ``` { "identifier": "dbc3577a-396e-11ef-b12b-da7ad0900002", "title": "Datadog Admin Role", "icon": "Datadog", "blueprint": "datadogRole", "team": [], "properties": { "createdAt": "2024-07-03T19:02:56.796169+00:00", "modifiedAt": "2024-07-03T19:02:56.797067+00:00", "custom": false, "userCount": 59 }, "relations": {} } ``` Default mappingYAML ``` - kind: team selector: query: 'true' port: entity: mappings: identifier: .id title: .id blueprint: '"datadogTeam"' - kind: team selector: query: 'true' includeMembers: 'true' port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogTeam"' properties: description: .attributes.description handle: .attributes.handle userCount: .attributes.user_count summary: .attributes.summary createdAt: .attributes.created_at | todate relations: members: if .__members then .__members[].id else [] end ``` Sample payloadJSON ``` { "type": "team", "attributes": { "description": "", "modified_at": "2024-12-09T08:44:00.526954+00:00", "is_managed": false, "link_count": 0, "user_count": 3, "name": "Dummy", "created_at": "2024-12-09T08:44:00.526949+00:00", "handle": "Dummy", "summary": null }, "relationships": { "user_team_permissions": { "links": { "related": "/api/v2/team/b7c4d123-a981-4b75-cd5b-86e246513f3c/permission-settings" } }, "team_links": { "links": { "related": "/api/v2/team/b7c4d123-a981-4b75-cd5b-86e246513f3c/links" } } }, "id": "b7c4d123-a981-4b75-cd5b-86e246513f3c", "__members": [ { "type": "users", "id": "579e8a70-4d70-11ef-b68c-f276c989c0b2", "attributes": { "name": "John Doe", "handle": "john.doe@example.com", "email": "john.doe@example.com", "icon": "", "disabled": false, "service_account": false } } ] } ``` Port entityJSON ``` { "identifier": "b7c4d123-a981-4b75-cd5b-86e246513f3c", "title": "Dummy", "team": [], "properties": { "description": "", "handle": "Dummy", "userCount": 3 }, "relations": { "members": [ "579e8a70-4d70-11ef-b68c-f276c989c0b2" ] }, "icon": "Datadog" } ``` Default mappingYAML ``` - kind: serviceDependency selector: query: 'true' environment: 'dev' startTime: 1 port: entity: mappings: identifier: .name | tostring title: .name blueprint: '"datadogServiceDependency"' properties: sourceService: .name relations: dependencies: '[.calls[] | tostring]' ``` Sample payloadJSON ``` { "service_a": { "calls": [ "service_b", "service_c" ] }, "service_b": { "calls": [ "service_o" ] }, "service_c": { "calls": [ "service_o" ] }, "service_o": { "calls": [] } } ``` Port entityJSON ``` { "identifier": "service_a", "title": "Dummy", "icon": "Datadog", "relations": { "dependencies": [ "service_b", "service_c" ] } } ``` Default mappingYAML ``` resources: - kind: organization selector: query: 'true' port: entity: mappings: identifier: .public_id | tostring title: .name blueprint: '"datadogOrganization"' properties: name: .name description: .description created: .created | gsub(" "; "T") | . + "Z" setting: .settings ``` Sample payloadJSON ``` { "public_id": "ad50beeb-64d6-11f1-8f1b-22b858bbc981", "name": "Port integrations demo", "description": null, "created": "2026-06-10 14:14:21", "subscription": { "id": null, "billing_plan_id": 0, "started": 1781100863, "expires": 1782396863, "finished": null, "type": "trial", "is_custom": false, "is_trial": true, "is_expired": false }, "billing": { "type": "unavailable" }, "settings": { "default_landing_page": "Get Started", "saml": { "enabled": false }, "saml_can_be_enabled": false, "saml_login_url": null, "saml_idp_metadata_uploaded": false, "saml_idp_endpoint": null, "saml_idp_metadata_valid_until": null, "custom_landing_page": null, "saml_idp_initiated_login": { "enabled": false }, "saml_strict_mode": { "enabled": false }, "saml_autocreate_access_role": "st", "saml_autocreate_users_domains": { "enabled": false, "domains": [] }, "private_widget_share": false, "manage_reports": "false" } } ``` Port entityJSON ``` { "identifier": "ad50beeb-64d6-11f1-8f1b-22b858bbc981", "title": "Port integrations demo", "blueprint": "datadog_organization", "properties": { "name": "Port integrations demo", "description": null, "created": "2026-06-10T14:14:21Z", "setting": { "saml": { "enabled": false }, "manage_reports": "false", "saml_strict_mode": { "enabled": false }, "saml_can_be_enabled": false, "default_landing_page": "Get Started", "private_widget_share": false, "saml_idp_initiated_login": { "enabled": false }, "saml_idp_metadata_uploaded": false, "saml_autocreate_access_role": "st", "saml_autocreate_users_domains": { "domains": [], "enabled": false } } }, "relations": {}, "createdAt": "2026-06-18T17:29:13.938Z", "createdBy": "YmYqw8lGnF6zDB0fQfNDb3nKgLO84I32", "updatedAt": "2026-06-18T17:29:13.938Z", "updatedBy": "YmYqw8lGnF6zDB0fQfNDb3nKgLO84I32" } ``` Service definitions vs APM services Port's Datadog integration syncs **Service** entities from [service definitions](https://docs.datadoghq.com/api/latest/service-definition/#get-all-service-definitions) in the Datadog Service Catalog API (via the `GET /api/v2/services/definitions` endpoint). That is separate from [APM services](https://docs.datadoghq.com/tracing/services/service_page/), which Datadog discovers automatically from APM tracing. The integration **does not** fetch APM-discovered services. ## Set up live events[​](#set-up-live-events "Direct link to Set up live events") * Hosted by Port * Self-hosted The Datadog integration keeps your catalog up to date in near real time through two complementary channels. Both are available for all installation methods. The two channels carry different event types and are not interchangeable. Configure both for full coverage: | Channel | Carries | | ------------------------- | ---------------------------------------------------------------------------------------------------- | | 1. Monitor notifications | Monitor **state transitions** (for example `OK` → `Alert` → `OK`) and service dependency updates | | 2. Audit trail forwarding | **Configuration** changes to monitors, SLOs, users, roles and teams, plus restriction policy changes | Monitor state transitions are only available through channel 1 The Datadog audit trail records user and API initiated actions, so a monitor changing state on its own does not appear in it. The `overallState` property on your monitor entities is therefore updated in real time only through channel 1; with channel 2 alone it refreshes on the next scheduled resync. ### 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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. ### 1. Monitor notifications (automatic) When the integration starts, it automatically registers a Datadog webhook and creates a monitor notification rule. Any monitor matching the configured scope sends state change events directly to Port - no manual setup required. Required application key permissions To register these objects automatically, the Datadog application key used by the integration must have the following permissions: | Permission | Used for | | ----------------------------- | ---------------------------------------------- | | `integrations_read` | Read existing webhook configuration | | `create_webhooks` | Create the Port webhook | | `manage_integrations` | Update the Port webhook | | `monitors_read` | List existing notification rules | | `monitor_config_policy_write` | Create or update the monitor notification rule | The following monitor alert types are supported: `ci_pipelines_alert`, `ci_tests_alert`, `composite_monitor`, `error_tracking_alert`, `event_alert`, `event_v2_alert`, `log_alert`, `monitor_slo_alert`, `metric_slo_alert`, `outlier_monitor`, `process_alert`, `query_alert_monitor`, `rum_alert`, `service_check`, `synthetics_alert`, `trace_analytics_alert`. Use the `monitorNotificationRuleScope` configuration parameter to control which monitors trigger notifications (default: `service:*`, which captures all service monitors). Example values: * `service:*` - all service monitors (default). * `service:my-service AND env:prod` - monitors for a specific service and environment only. Note that the default scope only matches monitors carrying a `service` tag. If your monitors are tagged differently, widen the scope accordingly, or those monitors will not send state change events to Port. #### Manual registration (optional) The webhook and the notification rule can also be created by a Datadog administrator rather than by the integration. The integration consumes the resulting events either way - automatic registration is a convenience, not a requirement. You might prefer manual registration if you: * manage Datadog resources as code, or through a change management process * want explicit control over the naming, scope, and lifecycle of both objects * want to reuse an existing webhook or notification rule instead of having a second one created * cannot grant the application key the write permissions listed above, for example under a read-only credential policy Automatic registration is the only part being replaced. You still need a live events base URL configured, and the application key still needs read access to monitors so the integration can fetch each monitor when an event arrives. **Registration steps (click to expand)** **Step 1: Create the webhook in Datadog** 1. In Datadog, go to **Integrations** > **Webhooks**. 2. Click **New Webhook** and fill in the following: * **Name**: Any name you choose. Note it down - you will reference it in the next step. * **URL**: Append `/integration/webhook/monitor-events` to your [live events base URL](#live-events-base-url). * **Payload**: Enable **Encode as JSON** and enter the payload below exactly as shown. * **Custom headers**: If you have set a `webhookSecret`, add the following, replacing `` with its value. Skip this if you have not set a webhook secret. ``` {"X-Port-Ocean-Webhook-Secret": ""} ``` 3. Save the webhook. The payload template: ``` { "id": "$ID", "message": "$TEXT_ONLY_MSG", "priority": "$PRIORITY", "last_updated": "$LAST_UPDATED", "event_type": "$EVENT_TYPE", "event_url": "$LINK", "service": "$HOSTNAME", "service_id": "$SERVICE_ID", "service_name": "$SERVICE_NAME", "creator": "$USER", "title": "$EVENT_TITLE", "date": "$DATE", "org_id": "$ORG_ID", "org_name": "$ORG_NAME", "alert_id": "$ALERT_ID", "alert_metric": "$ALERT_METRIC", "alert_status": "$ALERT_STATUS", "alert_title": "$ALERT_TITLE", "alert_type": "$ALERT_TYPE", "tags": "$TAGS", "body": "$EVENT_MSG" } ``` Use the payload exactly as shown Events missing `event_type` or `alert_id` are discarded, as are events missing `org_name` in multi-organization installations. Discards are silent, so an altered payload is difficult to diagnose. **Step 2: Create the monitor notification rule** 1. In Datadog, go to **Monitors** > **Settings** > **Notification Rules**. 2. Create a new rule and add the webhook from step 1 as a recipient. Webhook recipients take the form `@webhook-`, where `` is the webhook name from step 1. 3. Set the rule's scope to match the monitors you want to send state change events to Port. 4. Save the rule. Alternatively, add `@webhook-` to the notification message of each individual monitor you want covered. A notification rule is usually preferable, since it applies to many monitors at once. **Step 3: Disable automatic registration** Set the `skipWebhookCreation` configuration parameter to `true` (`integration.config.skipWebhookCreation` for Helm installations), so the integration no longer attempts automatic registration on startup. It continues to receive and process live events normally. ### 2. Audit trail forwarding (manual setup required) The integration processes Datadog audit trail events to reflect near-real-time changes to monitors, users, teams, SLOs, and roles in your catalog. | Catalog entity | Datadog audit event | | -------------- | -------------------------------------- | | Monitor | Monitor | | Monitor | Access Management (restriction policy) | | SLO | SLO | | SLO | Access Management (restriction policy) | | User | Access Management | | Role | Access Management | | Team | Teams Management | Manual setup required Datadog does not support managing audit trail forwarding destinations via its API. You must configure this destination manually in the Datadog UI, as described below. **To configure audit trail forwarding in Datadog:** 1. In Datadog, go to **Organization Settings** > **Compliance** > **Audit Trail Settings**. 2. Under **Forward Audit Events** > **Custom Destinations**, click **New Destination**. 3. Choose **HTTP** as the destination type and fill in the following: * **Destination URL**: Append `/integration/webhook/audit-trail` to your [live events base URL](#live-events-base-url). * **Query filter**: Enter the following query so that only events this integration handles are forwarded: ``` @asset.type:(monitor OR slo) OR @evt.name:("Access Management" OR "Teams Management") ``` * **Authentication**: Select **Request Header** and provide: * **Header name**: `X-Port-Ocean-Webhook-Secret` * **Header value**: The value of your `webhookSecret` configuration. If you have not set a webhook secret, enter any random string - Datadog requires a non-empty value, and the integration will accept all requests regardless. * **Enable GZIP compression for payloads**: Disable this option. It is enabled by default, but the integration expects uncompressed payloads. 4. Save the destination. ### BaseUrl & webhook configuration[​](#baseurl--webhook-configuration "Direct link to 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 Datadog 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 Datadog 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Datadog is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Datadog can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) The Datadog integration keeps your catalog up to date in near real time through two complementary channels. Both are available for all installation methods. The two channels carry different event types and are not interchangeable. Configure both for full coverage: | Channel | Carries | | ------------------------- | ---------------------------------------------------------------------------------------------------- | | 1. Monitor notifications | Monitor **state transitions** (for example `OK` → `Alert` → `OK`) and service dependency updates | | 2. Audit trail forwarding | **Configuration** changes to monitors, SLOs, users, roles and teams, plus restriction policy changes | Monitor state transitions are only available through channel 1 The Datadog audit trail records user and API initiated actions, so a monitor changing state on its own does not appear in it. The `overallState` property on your monitor entities is therefore updated in real time only through channel 1; with channel 2 alone it refreshes on the next scheduled resync. ### 1. Monitor notifications (automatic) When the integration starts, it automatically registers a Datadog webhook and creates a monitor notification rule. Any monitor matching the configured scope sends state change events directly to Port - no manual setup required. Required application key permissions To register these objects automatically, the Datadog application key used by the integration must have the following permissions: | Permission | Used for | | ----------------------------- | ---------------------------------------------- | | `integrations_read` | Read existing webhook configuration | | `create_webhooks` | Create the Port webhook | | `manage_integrations` | Update the Port webhook | | `monitors_read` | List existing notification rules | | `monitor_config_policy_write` | Create or update the monitor notification rule | The following monitor alert types are supported: `ci_pipelines_alert`, `ci_tests_alert`, `composite_monitor`, `error_tracking_alert`, `event_alert`, `event_v2_alert`, `log_alert`, `monitor_slo_alert`, `metric_slo_alert`, `outlier_monitor`, `process_alert`, `query_alert_monitor`, `rum_alert`, `service_check`, `synthetics_alert`, `trace_analytics_alert`. Use the `monitorNotificationRuleScope` configuration parameter to control which monitors trigger notifications (default: `service:*`, which captures all service monitors). Example values: * `service:*` - all service monitors (default). * `service:my-service AND env:prod` - monitors for a specific service and environment only. Note that the default scope only matches monitors carrying a `service` tag. If your monitors are tagged differently, widen the scope accordingly, or those monitors will not send state change events to Port. #### Manual registration (optional) The webhook and the notification rule can also be created by a Datadog administrator rather than by the integration. The integration consumes the resulting events either way - automatic registration is a convenience, not a requirement. You might prefer manual registration if you: * manage Datadog resources as code, or through a change management process * want explicit control over the naming, scope, and lifecycle of both objects * want to reuse an existing webhook or notification rule instead of having a second one created * cannot grant the application key the write permissions listed above, for example under a read-only credential policy Automatic registration is the only part being replaced. You still need a live events base URL configured, and the application key still needs read access to monitors so the integration can fetch each monitor when an event arrives. **Registration steps (click to expand)** **Step 1: Create the webhook in Datadog** 1. In Datadog, go to **Integrations** > **Webhooks**. 2. Click **New Webhook** and fill in the following: * **Name**: Any name you choose. Note it down - you will reference it in the next step. * **URL**: Append `/integration/webhook/monitor-events` to the base URL you configured in the `liveEvents.baseUrl` Helm parameter. * **Payload**: Enable **Encode as JSON** and enter the payload below exactly as shown. * **Custom headers**: If you have set a `webhookSecret`, add the following, replacing `` with its value. Skip this if you have not set a webhook secret. ``` {"X-Port-Ocean-Webhook-Secret": ""} ``` 3. Save the webhook. The payload template: ``` { "id": "$ID", "message": "$TEXT_ONLY_MSG", "priority": "$PRIORITY", "last_updated": "$LAST_UPDATED", "event_type": "$EVENT_TYPE", "event_url": "$LINK", "service": "$HOSTNAME", "service_id": "$SERVICE_ID", "service_name": "$SERVICE_NAME", "creator": "$USER", "title": "$EVENT_TITLE", "date": "$DATE", "org_id": "$ORG_ID", "org_name": "$ORG_NAME", "alert_id": "$ALERT_ID", "alert_metric": "$ALERT_METRIC", "alert_status": "$ALERT_STATUS", "alert_title": "$ALERT_TITLE", "alert_type": "$ALERT_TYPE", "tags": "$TAGS", "body": "$EVENT_MSG" } ``` Use the payload exactly as shown Events missing `event_type` or `alert_id` are discarded, as are events missing `org_name` in multi-organization installations. Discards are silent, so an altered payload is difficult to diagnose. **Step 2: Create the monitor notification rule** 1. In Datadog, go to **Monitors** > **Settings** > **Notification Rules**. 2. Create a new rule and add the webhook from step 1 as a recipient. Webhook recipients take the form `@webhook-`, where `` is the webhook name from step 1. 3. Set the rule's scope to match the monitors you want to send state change events to Port. 4. Save the rule. Alternatively, add `@webhook-` to the notification message of each individual monitor you want covered. A notification rule is usually preferable, since it applies to many monitors at once. **Step 3: Disable automatic registration** Set the `skipWebhookCreation` configuration parameter to `true` (`integration.config.skipWebhookCreation` for Helm installations), so the integration no longer attempts automatic registration on startup. It continues to receive and process live events normally. ### 2. Audit trail forwarding (manual setup required) The integration processes Datadog audit trail events to reflect near-real-time changes to monitors, users, teams, SLOs, and roles in your catalog. | Catalog entity | Datadog audit event | | -------------- | -------------------------------------- | | Monitor | Monitor | | Monitor | Access Management (restriction policy) | | SLO | SLO | | SLO | Access Management (restriction policy) | | User | Access Management | | Role | Access Management | | Team | Teams Management | Manual setup required Datadog does not support managing audit trail forwarding destinations via its API. You must configure this destination manually in the Datadog UI, as described below. **To configure audit trail forwarding in Datadog:** 1. In Datadog, go to **Organization Settings** > **Compliance** > **Audit Trail Settings**. 2. Under **Forward Audit Events** > **Custom Destinations**, click **New Destination**. 3. Choose **HTTP** as the destination type and fill in the following: * **Destination URL**: Append `/integration/webhook/audit-trail` to the base URL you configured in the `liveEvents.baseUrl` Helm parameter. * **Query filter**: Enter the following query so that only events this integration handles are forwarded: ``` @asset.type:(monitor OR slo) OR @evt.name:("Access Management" OR "Teams Management") ``` * **Authentication**: Select **Request Header** and provide: * **Header name**: `X-Port-Ocean-Webhook-Secret` * **Header value**: The value of your `webhookSecret` configuration. If you have not set a webhook secret, enter any random string - Datadog requires a non-empty value, and the integration will accept all requests regardless. * **Enable GZIP compression for payloads**: Disable this option. It is enabled by default, but the integration expects uncompressed payloads. 4. Save the destination. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found on the datadog [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/examples/.md) ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=Datadog). --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ## Monitor[​](#monitor "Direct link to Monitor") **Monitor blueprint (click to expand)** Create in Port ``` { "identifier": "datadogMonitor", "description": "This blueprint represents a datadog monitor", "title": "Datadog Monitor", "icon": "Datadog", "schema": { "properties": { "monitorType": { "type": "string", "title": "Monitor Type" }, "tags": { "type": "array", "title": "Tags" }, "overallState": { "type": "string", "title": "Overall state", "enum": [ "Alert", "Ignored", "No Data", "OK", "Skipped", "Unknown", "Warn" ], "enumColors": { "Alert": "red", "Ignored": "darkGray", "No Data": "lightGray", "OK": "green", "Skipped": "yellow", "Unknown": "purple", "Warn": "orange" } }, "priority": { "type": "string", "title": "Priority" }, "thresholds": { "type": "object", "title": "Thresholds" }, "createdBy": { "type": "string", "title": "Creator" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: monitor selector: query: "true" port: entity: mappings: blueprint: '"datadogMonitor"' identifier: .id | tostring title: .name properties: tags: .tags monitorType: .type overallState: .overall_state thresholds: .thresholds priority: .priority createdBy: .creator.email createdAt: .created updatedAt: .modified ``` ### Restriction policy enrichment[​](#restriction-policy-enrichment "Direct link to Restriction policy enrichment") Set `includeRestrictionPolicy: "true"` in the selector to enrich each resource with its [Datadog restriction policy](https://docs.datadoghq.com/api/latest/restriction-policies/). This adds three custom fields to the raw data: | Field | Description | | ---------------------- | --------------------------------------------------------------- | | `.__restrictionPolicy` | The full restriction policy object, including all bindings. | | `.__restrictedUsers` | Deduplicated list of user IDs that have access to the resource. | | `.__restrictedTeams` | Deduplicated list of team IDs that have access to the resource. | Role IDs are already present in the native payload as `.restricted_roles` without enabling this option. Performance note Enrichment requires one extra API call per resource and is disabled by default. ## Service[​](#service "Direct link to Service") **Service blueprint (click to expand)** Create in Port ``` { "identifier": "datadogService", "description": "This blueprint represents a Datadog service", "title": "Datadog Service", "icon": "Datadog", "schema": { "properties": { "application": { "title": "Application", "type": "string" }, "description": { "title": "Description", "type": "string" }, "tags": { "type": "array", "items": { "type": "string" }, "title": "Tags" }, "languages": { "items": { "type": "string" }, "title": "Languages", "type": "array" }, "type": { "title": "Type", "type": "string", "enum": [ "web", "db", "custom", "cache", "function", "browser", "mobile" ], "enumColors": { "web": "lightGray", "db": "lightGray", "custom": "lightGray", "cache": "lightGray", "function": "lightGray", "browser": "lightGray", "mobile": "lightGray" } }, "owners": { "type": "array", "title": "Service Owners", "items": { "type": "string" } }, "links": { "title": "Service Links", "type": "array", "description": "Links to external resources and repositories", "items": { "type": "string", "format": "url" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: service selector: query: "true" port: entity: mappings: blueprint: '"datadogService"' identifier: .attributes.schema."dd-service" title: .attributes.schema."dd-service" properties: application: .attributes.schema.application languages: .attributes.schema.languages description: .attributes.schema.description tags: .attributes.schema.tags type: .attributes.schema.type links: .attributes.schema.links | map(.url) owners: '[.attributes.schema.contacts[] | select(.type == "email") | .contact]' ``` Service definitions vs APM services Port's Datadog integration syncs **Service** entities from [service definitions](https://docs.datadoghq.com/api/latest/service-definition/#get-all-service-definitions) in the Datadog Service Catalog API (via the `GET /api/v2/services/definitions` endpoint). That is separate from [APM services](https://docs.datadoghq.com/tracing/services/service_page/), which Datadog discovers automatically from APM tracing. The integration **does not** fetch APM-discovered services. ## SLO[​](#slo "Direct link to SLO") **SLO blueprint (click to expand)** Create in Port ``` { "identifier": "datadogSlo", "description": "This blueprint represents a datadog SLO", "title": "Datadog SLO", "icon": "Datadog", "schema": { "properties": { "tags": { "type": "array", "title": "Tags" }, "sloType": { "title": "Type", "type": "string" }, "description": { "title": "Description", "type": "string" }, "warningThreshold": { "icon": "DefaultProperty", "title": "Warning Threshold", "type": "number" }, "targetThreshold": { "icon": "DefaultProperty", "title": "Target Threshold", "type": "number" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "createdBy": { "title": "Creator", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": { "sli_average": { "title": "SLI Average", "type": "number", "target": "datadogSloHistory", "calculationSpec": { "func": "average", "averageOf": "total", "property": "sliValue", "measureTimeBy": "$createdAt", "calculationBy": "property" } } }, "relations": { "monitors": { "title": "SLO Monitors", "description": "The monitors tracking this SLO", "target": "datadogMonitor", "required": false, "many": true }, "services": { "title": "Services", "description": "The services tracked by this SLO", "target": "datadogService", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: slo selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"datadogSlo"' properties: tags: .tags sloType: .type description: .description warningThreshold: .warning_threshold targetThreshold: .target_threshold createdBy: .creator.email createdAt: .created_at | todate updatedAt: .modified_at | todate relations: monitors: .monitor_ids | map(tostring) services: >- .monitor_tags + .tags | map(select(startswith("service:"))) | unique | map(split(":")[1]) ``` Service Relation Based on the [best practices for tagging infrastructure](https://www.datadoghq.com/blog/tagging-best-practices/), the default mapping connects SLOs to services using tags that starts with the `service` keyword. ### Restriction policy enrichment[​](#restriction-policy-enrichment "Direct link to Restriction policy enrichment") Set `includeRestrictionPolicy: "true"` in the selector to enrich each resource with its [Datadog restriction policy](https://docs.datadoghq.com/api/latest/restriction-policies/). This adds three custom fields to the raw data: | Field | Description | | ---------------------- | --------------------------------------------------------------- | | `.__restrictionPolicy` | The full restriction policy object, including all bindings. | | `.__restrictedUsers` | Deduplicated list of user IDs that have access to the resource. | | `.__restrictedTeams` | Deduplicated list of team IDs that have access to the resource. | Role IDs are already present in the native payload as `.restricted_roles` without enabling this option. Performance note Enrichment requires one extra API call per resource and is disabled by default. ## SLO history[​](#slo-history "Direct link to SLO history") **SLO history blueprint (click to expand)** Create in Port ``` { "identifier": "datadogSloHistory", "description": "This blueprint represents a datadog SLO history", "title": "Datadog SLO History", "icon": "Datadog", "schema": { "properties": { "monitor_type": { "icon": "DefaultProperty", "title": "Type", "type": "string" }, "sliValue": { "icon": "DefaultProperty", "title": "SLI Value", "type": "number" }, "sampling_start_date": { "icon": "DefaultProperty", "type": "string", "title": "Sampling Start Date", "format": "date-time" }, "sampling_end_date": { "icon": "DefaultProperty", "type": "string", "title": "Sampling End Date", "format": "date-time" } }, "required": [] }, "mirrorProperties": { "slo_target": { "title": "SLO Target", "path": "slo.targetThreshold" }, "slo_warning_threshold": { "title": "SLO Warning Threshold", "path": "slo.warningThreshold" } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "slo": { "title": "SLO", "description": "The SLO to which this history belongs to", "target": "datadogSlo", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** Configuration Options The SLO history selector supports two time-related configurations: * `timeframe`: How many days to look back for each SLO history data point. Must be greater than 0 (default: 7 days) * `periodOfTimeInMonths`: How far back in time to fetch SLO history. Must be between 1-12 months (default: 6 months) ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: sloHistory selector: query: "true" timeframe: 7 periodOfTimeInMonths: 6 port: entity: mappings: identifier: .slo.id | tostring title: .slo.name blueprint: '"datadogSloHistory"' properties: monitory_type: .type sampling_start_date: .from_ts | todate sampling_end_date: .to_ts | todate sliValue: .overall.sli_value relations: slo: .slo.id ``` ## Service Metric[​](#service-metric "Direct link to Service Metric") **Service Metric blueprint (click to expand)** Create in Port ``` { "identifier": "datadogServiceMetric", "description": "This blueprint represents a Datadog service metric", "title": "Datadog Service Metric", "icon": "Datadog", "schema": { "properties": { "query": { "type": "string", "title": "Query", "description": "The Datadog query used to retrieve this metric" }, "series": { "type": "array", "title": "Series", "description": "Array containing detailed information about the metric series" }, "res_type": { "type": "string", "title": "Response Type", "description": "The type of response from the Datadog API" }, "from_date": { "type": "string", "format": "date-time", "title": "From Date", "description": "Unix timestamp of the start of the queried time period" }, "to_date": { "type": "string", "format": "date-time", "title": "To Date", "description": "Unix timestamp of the end of the queried time period" }, "env": { "type": "string", "title": "Environment", "description": "The environment of the service" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "service": { "title": "Service", "description": "The service associated with this query", "target": "datadogService", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** Configuration Mapping for Flexible Metric Fetching The `datadogSelector` section within each `serviceMetric` resource demonstrates how to fetch multiple metrics (e.g., `system.mem.used`, `system.disk.used`) from Datadog with a variety of filters. You can control the: * **Metric:** Specify the exact metric name (e.g., `avg:system.mem.used`) * **Environment:** Filter by specific environments (e.g., `prod`, or use `*` for all) * **Service:** Filter by specific services in your [datadog service catalog](https://docs.datadoghq.com/service_catalog/) (use `*` for all) * **Timeframe:** Define the time range for data retrieval (in minutes) This configuration allows you to tailor your data fetching to specific needs and scenarios. **Note**: The `env` and `service` filters let you specify custom tag names in your Datadog account. For example, your service tag could be `servicename`, and your environment tag could be `envt` or `environment`. ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: serviceMetric selector: query: "true" datadogSelector: metric: "avg:system.mem.used" env: tag: env value: "*" service: tag: servicename value: "*" timeframe: 10 port: entity: mappings: blueprint: '"datadogServiceMetric"' identifier: .__query_id title: .query properties: query: .__query series: .series res_type: .res_type from_date: ".from_date / 1000 | todate" to_date: ".to_date / 1000 | todate" env: .__env relations: service: .__service - kind: serviceMetric selector: query: "true" datadogSelector: metric: "avg:system.disk.used" env: tag: env value: "prod" service: tag: servicename value: "*" timeframe: 5 port: entity: mappings: blueprint: '"datadogServiceMetric"' identifier: .__query_id title: .query properties: query: .__query series: .series res_type: .res_type from_date: ".from_date / 1000 | todate" to_date: ".to_date / 1000 | todate" env: .__env relations: service: .__service ``` Service Relation Based on the [best practices for tagging infrastructure](https://www.datadoghq.com/blog/tagging-best-practices/), the default JQ maps service metrics to services using tags that starts with the `service` keyword ## Users[​](#users "Direct link to Users") **User blueprint (click to expand)** Create in Port ``` { "identifier": "datadogUser", "description": "This blueprint represents a Datadog user account. Users can be assigned to teams, granted specific permissions, and can interact with various Datadog features based on their access levels.", "title": "Datadog User", "icon": "Datadog", "schema": { "properties": { "email": { "type": "string", "format": "email", "title": "Email", "description": "The email address associated with the user account" }, "handle": { "type": "string", "title": "Handle", "description": "The unique handle identifier for the user within Datadog" }, "status": { "type": "string", "title": "Status", "description": "The current status of the user account (e.g., active, pending, disabled)" }, "disabled": { "type": "boolean", "title": "Disabled", "description": "Indicates whether the user account is currently disabled" }, "verified": { "type": "boolean", "title": "Verified", "description": "Indicates whether the user's email address has been verified" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "The timestamp when the user account was created" } }, "required": [] } } ``` **Integration configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true resources: - kind: user selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogUser"' properties: email: .attributes.email handle: .attributes.handle status: .attributes.status disabled: .attributes.disabled verified: .attributes.verified createdAt: .attributes.created_at ``` ## Role[​](#role "Direct link to Role") **Role blueprint (click to expand)** Create in Port ``` { "identifier": "datadogRole", "description": "This blueprint represents a Datadog role", "title": "Datadog Role", "icon": "Datadog", "schema": { "properties": { "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "The timestamp when the role was created" }, "modifiedAt": { "type": "string", "format": "date-time", "title": "Modified At", "description": "The timestamp when the role was last modified" }, "custom": { "type": "boolean", "title": "Custom role", "description": "Indicates whether this is a user-defined role. When false, the role is a Datadog built-in role." }, "userCount": { "type": "number", "title": "User Count", "description": "The total number of users assigned to this role" } }, "required": [] } } ``` **Integration configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true resources: - kind: role selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogRole"' properties: createdAt: .attributes.created_at modifiedAt: .attributes.modified_at custom: if .attributes.managed == true then false else true end userCount: .attributes.user_count ``` ### Enrich roles with their users[​](#enrich-roles-with-their-users "Direct link to Enrich roles with their users") By default, roles are ingested without their associated users. To include them, set `includeUsers: true` in the selector: ``` resources: - kind: role selector: query: "true" includeUsers: true ``` When enabled, each role's raw data includes an additional field with the list of [users](https://docs.datadoghq.com/api/latest/roles/get-all-users-of-a-role/) assigned to that role: | Field | Description | | ---------- | ----------------------------------------------- | | `.__users` | The list of users that have access to the role. | Performance impact This enrichment makes an additional API call for each role, which can slow down the sync. It is disabled by default. ## Team[​](#team "Direct link to Team") **Team blueprint (click to expand)** Create in Port ``` { "identifier": "datadogTeam", "description": "This blueprint represents a Datadog team", "title": "Datadog Team", "icon": "Datadog", "schema": { "properties": { "description": { "type": "string", "title": "Description", "description": "A description of the team's purpose and responsibilities" }, "handle": { "type": "string", "title": "Handle", "description": "The unique handle identifier for the team within Datadog" }, "userCount": { "type": "number", "title": "User Count", "description": "The total number of users that are members of this team" }, "summary": { "type": "string", "title": "Summary", "description": "A brief summary of the team's purpose or main responsibilities" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "The timestamp when the team was created" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "members": { "target": "datadogUser", "title": "Members", "description": "Users who are members of this team", "many": true, "required": false } } } ``` **Integration configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true resources: - kind: team selector: query: 'true' includeMembers: 'true' port: entity: mappings: identifier: .id | tostring title: .attributes.name blueprint: '"datadogTeam"' properties: description: .attributes.description handle: .attributes.handle userCount: .attributes.user_count summary: .attributes.summary createdAt: .attributes.created_at relations: members: if .__members then [.__members[] | .id] else [] end ``` ## Service Dependency[​](#service-dependency "Direct link to Service Dependency") **Service Dependency blueprint (click to expand)** Create in Port ``` { "identifier": "datadogServiceDependency", "description": "This blueprint represents a dependency relationship between Datadog services, where one service calls another.", "title": "Datadog Service Dependency", "icon": "Datadog", "schema": { "properties": {}, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "dependencies": { "title": "Depends on", "description": "The services called by the source service", "target": "datadogService", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true resources: - kind: serviceDependency selector: query: 'true' environment: 'dev' startTime: 1 port: entity: mappings: identifier: .name | tostring title: .name blueprint: '"datadogServiceDependency"' properties: sourceService: .name relations: dependencies: '[.calls[] | tostring]' ``` * Include Environment * Include Start Time The `environment` selector is crucial for accurately mapping service dependencies from Datadog's Service Map. Datadog's Service Map visualizes how services interact within a specific environment (e.g., `prod`, `staging`, `dev`). By specifying an `environment` in the selector, you ensure that only the dependencies relevant to that particular environment are fetched and mapped, preventing the inclusion of irrelevant or duplicate dependency information from other environments. By default, this selector is set to `prod`. ``` - kind: serviceDependency selector: query: 'true' environment: 'dev' startTime: 1 ``` The `startTime` selector is crucial for fetching service dependency data from a specific point in time. This value represents the number of hours to look back from the current time to fetch dependency data. By default, this selector is set to `1` hour. ``` - kind: serviceDependency selector: query: 'true' environment: 'dev' startTime: 1 ``` --- # Dynatrace ![Dynatrace icon](/img/guides/icons/Dynatrace.svg)![Dynatrace icon](/img/guides/icons/Dynatrace.svg) Loading version... Port's Dynatrace integration allows you to model Dynatrace resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Problem](https://docs.dynatrace.com/docs/dynatrace-api/environment-api/problems-v2/problems/get-problems-list#definition--Problem)[Entity](https://docs.dynatrace.com/docs/dynatrace-api/environment-api/entity-v2/get-entities-list#definition--Entity)[SLO](https://docs.dynatrace.com/docs/dynatrace-api/environment-api/service-level-objectives/get-all#definition--SLO)[Team](https://docs.dynatrace.com/docs/discover-dynatrace/references/dynatrace-api/environment-api/settings/objects/get-object) ## Core support [Live eventsSee supported resources](#configure-real-time-updates) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### Generate a Dynatrace API key[​](#generate-a-dynatrace-api-key "Direct link to Generate a Dynatrace API key") 1. Navigate to `/ui/apps/dynatrace.classic.tokens/ui/access-tokens`. For example, if you access your Dynatrace instance at `https://npm82883.apps.dynatrace.com`, you should navigate to `https://npm82883.apps.dynatrace.com/ui/apps/dynatrace.classic.tokens/ui/access-tokens`. 2. Click **Generate new token** to create a new token. Ensure the permissions: `DataExport`, `Read entities`, `Read problems`, `Read SLO` and `Read settings` are assigned to the token. The `DataExport` permission allows Dynatrace to perform healthchecks before ingestion starts. The `Read settings` scope allows the integration to ingest teams. ### Construct your Dynatrace Host URL[​](#construct-your-dynatrace-host-url "Direct link to Construct your Dynatrace Host URL") Your Dynatrace host URL should be `https://.live.dynatrace.com`. Note that there is a difference between the instance URL and the API host URL. The former contains `apps` while the latter (as shown prior) uses `live`. This means if your environment ID is `npm82883`, your API host URL should be `https://npm82883.live.dynatrace.com`. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: entity selector: query: 'true' entityFields: firstSeenTms,lastSeenTms,tags entityTypes: - APPLICATION - SERVICE port: entity: mappings: identifier: .entityId title: .displayName blueprint: '"dynatraceEntity"' properties: firstSeen: (.firstSeenTms // 0) / 1000 | todate lastSeen: (.lastSeenTms // 0) / 1000 | todate type: .type tags: '[(.tags // [])[].stringRepresentation]' - kind: problem selector: query: 'true' port: entity: mappings: identifier: .problemId title: .title blueprint: '"dynatraceProblem"' properties: entityTags: .entityTags[].stringRepresentation evidenceDetails: .evidenceDetails.details // [] | .[].displayName managementZones: .managementZones[].name problemFilters: .problemFilters[].name severityLevel: .severityLevel status: .status startTime: .startTime / 1000 | todate endTime: .endTime | if . == -1 then null else (./1000 | todate) end relations: impactedEntities: .impactedEntities[].entityId.id linkedProblemInfo: .linkedProblemInfo.problemId rootCauseEntity: .rootCauseEntity.entityId.id - kind: slo selector: query: 'true' attachRelatedEntities: true port: entity: mappings: identifier: .id title: .name blueprint: '"dynatraceSlo"' properties: status: .status target: .target enabled: .enabled warning: .warning error: .error errorBudget: .errorBudget evaluatedPercentage: .evaluatedPercentage evaluationType: .evaluationType filter: .filter relations: entities: if .__entities != null then .__entities | map(.entityId) else [] end ``` ### Ingest additional resource types[​](#ingest-additional-resource-types "Direct link to Ingest additional resource types") By default, the `entity` kind ingests only entities of type `APPLICATION` and `SERVICE` due to the large number of available resources. However, you can configure the `entity` kind mapping to ingest entities of other types. To do this, use the `entityTypes` selector in the entity mapping like so: **Mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: entity selector: query: "true" entityTypes: ["APPLICATION", "SERVICE"] port: entity: mappings: identifier: .entityId title: .displayName blueprint: '"dynatraceEntity"' properties: firstSeen: ".firstSeenTms / 1000 | todate" lastSeen: ".lastSeenTms / 1000 | todate" type: .type tags: '[(.tags // [])[].stringRepresentation]' managementZones: .managementZones[].name properties: .properties fromRelationships: .fromRelationships toRelationships: .toRelationships ``` #### Available resource types[​](#available-resource-types "Direct link to Available resource types") You can retrieve a list of available resource types by using the [Dynatrace Entity Types API](https://docs.dynatrace.com/docs/dynatrace-api/environment-api/entity-v2/get-entity-type#definition--EntityType). Below is a list of resource types retrieved from the API: **Dynatrace entity types (click to expand)** * `APM_SECURITY_GATEWAY` * `APPLICATION` * `APPLICATION_METHOD` * `APPLICATION_METHOD_GROUP` * `APPMON_SERVER` * `APPMON_SYSTEM_PROFILE` * `AUTO_SCALING_GROUP` * `AWS_APPLICATION_LOAD_BALANCER` * `AWS_AVAILABILITY_ZONE` * `AWS_CREDENTIALS` * `AWS_LAMBDA_FUNCTION` * `AWS_NETWORK_LOAD_BALANCER` * `AZURE_API_MANAGEMENT_SERVICE` * `AZURE_APPLICATION_GATEWAY` * `AZURE_APP_SERVICE_PLAN` * `AZURE_COSMOS_DB` * `AZURE_CREDENTIALS` * `AZURE_EVENT_HUB` * `AZURE_EVENT_HUB_NAMESPACE` * `AZURE_FUNCTION_APP` * `AZURE_IOT_HUB` * `AZURE_LOAD_BALANCER` * `AZURE_MGMT_GROUP` * `AZURE_REDIS_CACHE` * `AZURE_REGION` * `AZURE_SERVICE_BUS_NAMESPACE` * `AZURE_SERVICE_BUS_QUEUE` * `AZURE_SERVICE_BUS_TOPIC` * `AZURE_SQL_DATABASE` * `AZURE_SQL_ELASTIC_POOL` * `AZURE_SQL_SERVER` * `AZURE_STORAGE_ACCOUNT` * `AZURE_SUBSCRIPTION` * `AZURE_TENANT` * `AZURE_VM` * `AZURE_VM_SCALE_SET` * `AZURE_WEB_APP` * `BOSH_DEPLOYMENT` * `BROWSER` * `CF_FOUNDATION` * `CINDER_VOLUME` * `CLOUD_APPLICATION` * `CLOUD_APPLICATION_INSTANCE` * `CLOUD_APPLICATION_NAMESPACE` * `CONTAINER_GROUP` * `CONTAINER_GROUP_INSTANCE` * `CUSTOM_APPLICATION` * `CUSTOM_DEVICE` * `CUSTOM_DEVICE_GROUP` * `DATASTORE` * `DCRUM_APPLICATION` * `DCRUM_SERVICE` * `DCRUM_SERVICE_INSTANCE` * `DEVICE_APPLICATION_METHOD` * `DEVICE_APPLICATION_METHOD_GROUP` * `DISK` * `DOCKER_CONTAINER_GROUP` * `DOCKER_CONTAINER_GROUP_INSTANCE` * `DYNAMO_DB_TABLE` * `EBS_VOLUME` * `EC2_INSTANCE` * `ELASTIC_LOAD_BALANCER` * `ENVIRONMENT` * `EXTERNAL_SYNTHETIC_TEST` * `EXTERNAL_SYNTHETIC_TEST_STEP` * `GCP_ZONE` * `GEOLOCATION` * `GEOLOC_SITE` * `GOOGLE_COMPUTE_ENGINE` * `HOST` * `HOST_GROUP` * `HTTP_CHECK` * `HTTP_CHECK_STEP` * `HYPERVISOR` * `HYPERVISOR_CLUSTER` * `HYPERVISOR_DISK` * `KUBERNETES_CLUSTER` * `KUBERNETES_NODE` * `KUBERNETES_SERVICE` * `MOBILE_APPLICATION` * `MULTIPROTOCOL_MONITOR` * `NETWORK_INTERFACE` * `NEUTRON_SUBNET` * `OPENSTACK_AVAILABILITY_ZONE` * `OPENSTACK_COMPUTE_NODE` * `OPENSTACK_PROJECT` * `OPENSTACK_REGION` * `OPENSTACK_VM` * `OS` * `PROCESS_GROUP` * `PROCESS_GROUP_INSTANCE` * `QUEUE` * `QUEUE_INSTANCE` * `RELATIONAL_DATABASE_SERVICE` * `RUNTIME_COMPONENT` * `S3BUCKET` * `SERVICE` * `SERVICE_INSTANCE` * `SERVICE_METHOD` * `SERVICE_METHOD_GROUP` * `SOFTWARE_COMPONENT` * `SWIFT_CONTAINER` * `SYNTHETIC_LOCATION` * `SYNTHETIC_TEST` * `SYNTHETIC_TEST_STEP` * `VCENTER` * `VIRTUALMACHINE` * `VMWARE_DATACENTER` * `cloud:aws:acmprivateca` * `cloud:aws:api_gateway` * `cloud:aws:app_runner` * `cloud:aws:appstream` * `cloud:aws:appsync` * `cloud:aws:athena` * `cloud:aws:aurora` * `cloud:aws:autoscaling` * `cloud:aws:billing` * `cloud:aws:cassandra` * `cloud:aws:chatbot` * `cloud:aws:cloud_front` * `cloud:aws:cloudhsm` * `cloud:aws:cloudsearch` * `cloud:aws:codebuild` * `cloud:aws:cognito` * `cloud:aws:connect` * `cloud:aws:datasync` * `cloud:aws:dax` * `cloud:aws:dms` * `cloud:aws:documentdb` * `cloud:aws:dxcon` * `cloud:aws:dynamodb` * `cloud:aws:ebs` * `cloud:aws:ec2_spot` * `cloud:aws:ec2api` * `cloud:aws:ecs` * `cloud:aws:ecs:cluster` * `cloud:aws:efs` * `cloud:aws:eks:cluster` * `cloud:aws:elasticache` * `cloud:aws:elasticbeanstalk` * `cloud:aws:elasticinference` * `cloud:aws:elastictranscoder` * `cloud:aws:emr` * `cloud:aws:es` * `cloud:aws:events` * `cloud:aws:fsx` * `cloud:aws:gamelift` * `cloud:aws:glue` * `cloud:aws:inspector` * `cloud:aws:iot` * `cloud:aws:iot_things_graph` * `cloud:aws:iotanalytics` * `cloud:aws:kafka` * `cloud:aws:kinesis:data_analytics` * `cloud:aws:kinesis:data_firehose` * `cloud:aws:kinesis:data_stream` * `cloud:aws:kinesis:video_stream` * `cloud:aws:lambda` * `cloud:aws:lex` * `cloud:aws:logs` * `cloud:aws:media_tailor` * `cloud:aws:mediaconnect` * `cloud:aws:mediaconvert` * `cloud:aws:mediapackagelive` * `cloud:aws:mediapackagevod` * `cloud:aws:mq` * `cloud:aws:nat_gateway` * `cloud:aws:neptune` * `cloud:aws:opsworks` * `cloud:aws:polly` * `cloud:aws:qldb` * `cloud:aws:rds` * `cloud:aws:redshift` * `cloud:aws:rekognition` * `cloud:aws:robomaker` * `cloud:aws:route53` * `cloud:aws:route53resolver` * `cloud:aws:s3` * `cloud:aws:sage_maker:batch_transform_job` * `cloud:aws:sage_maker:endpoint` * `cloud:aws:sage_maker:endpoint_instance` * `cloud:aws:sage_maker:ground_truth` * `cloud:aws:sage_maker:processing_job` * `cloud:aws:sage_maker:training_job` * `cloud:aws:servicecatalog` * `cloud:aws:ses` * `cloud:aws:sns` * `cloud:aws:sqs` * `cloud:aws:ssm-runcommand` * `cloud:aws:states` * `cloud:aws:storagegateway` * `cloud:aws:swf` * `cloud:aws:textract` * `cloud:aws:transfer` * `cloud:aws:transitgateway` * `cloud:aws:translate` * `cloud:aws:trustedadvisor` * `cloud:aws:usage` * `cloud:aws:vpn` * `cloud:aws:waf` * `cloud:aws:wafv2` * `cloud:aws:workmail` * `cloud:aws:workspaces` * `cloud:azure:apimanagement:service` * `cloud:azure:app:containerapps` * `cloud:azure:app:managedenvironments` * `cloud:azure:appconfiguration:configurationstores` * `cloud:azure:appplatform:spring` * `cloud:azure:automation:automationaccounts` * `cloud:azure:batch:account` * `cloud:azure:blockchain:blockchainmembers` * `cloud:azure:cache:redis` * `cloud:azure:cdn:cdnwebapplicationfirewallpolicies` * `cloud:azure:cdn:profiles` * `cloud:azure:classic_storage_account` * `cloud:azure:classic_storage_account:blob` * `cloud:azure:classic_storage_account:file` * `cloud:azure:classic_storage_account:queue` * `cloud:azure:classic_storage_account:table` * `cloud:azure:classic_virtual_machine` * `cloud:azure:cognitiveservices:allinone` * `cloud:azure:cognitiveservices:anomalydetector` * `cloud:azure:cognitiveservices:bingautosuggest` * `cloud:azure:cognitiveservices:bingcustomsearch` * `cloud:azure:cognitiveservices:bingentitysearch` * `cloud:azure:cognitiveservices:bingsearch` * `cloud:azure:cognitiveservices:bingspellcheck` * `cloud:azure:cognitiveservices:computervision` * `cloud:azure:cognitiveservices:contentmoderator` * `cloud:azure:cognitiveservices:customvisionprediction` * `cloud:azure:cognitiveservices:customvisiontraining` * `cloud:azure:cognitiveservices:face` * `cloud:azure:cognitiveservices:immersivereader` * `cloud:azure:cognitiveservices:inkrecognizer` * `cloud:azure:cognitiveservices:luis` * `cloud:azure:cognitiveservices:luisauthoring` * `cloud:azure:cognitiveservices:openai` * `cloud:azure:cognitiveservices:personalizer` * `cloud:azure:cognitiveservices:qnamaker` * `cloud:azure:cognitiveservices:speech` * `cloud:azure:cognitiveservices:textanalytics` * `cloud:azure:cognitiveservices:translator` * `cloud:azure:containerinstance:containergroup` * `cloud:azure:containerregistry:registries` * `cloud:azure:containerservice:managedcluster` * `cloud:azure:datafactory:v1` * `cloud:azure:datafactory:v2` * `cloud:azure:datalakeanalytics:accounts` * `cloud:azure:datalakestore:accounts` * `cloud:azure:datashare:accounts` * `cloud:azure:devices:iothubs` * `cloud:azure:devices:provisioningservices` * `cloud:azure:documentdb:databaseaccounts:global` * `cloud:azure:documentdb:databaseaccounts:mongo` * `cloud:azure:eventgrid:domains` * `cloud:azure:eventgrid:systemtopics` * `cloud:azure:eventgrid:topics` * `cloud:azure:eventhub:clusters` * `cloud:azure:frontdoor` * `cloud:azure:hdinsight:cluster` * `cloud:azure:hybridcompute:machines` * `cloud:azure:insights:components` * `cloud:azure:iotcentral:iotapps` * `cloud:azure:keyvault:vaults` * `cloud:azure:kusto:clusters` * `cloud:azure:logic:integrationserviceenvironments` * `cloud:azure:logic:workflows` * `cloud:azure:machinelearningservices:workspaces` * `cloud:azure:maps:accounts` * `cloud:azure:mariadb:server` * `cloud:azure:media:mediaservices` * `cloud:azure:media:mediaservices:streamingendpoints` * `cloud:azure:mysql:flexibleservers` * `cloud:azure:mysql:server` * `cloud:azure:netapp:netappaccounts:capacitypools` * `cloud:azure:netapp:netappaccounts:capacitypools:volumes` * `cloud:azure:network:applicationgateways` * `cloud:azure:network:azurefirewalls` * `cloud:azure:network:dnszones` * `cloud:azure:network:expressroutecircuits` * `cloud:azure:network:loadbalancers:basic` * `cloud:azure:network:loadbalancers:gateway` * `cloud:azure:network:loadbalancers:standard` * `cloud:azure:network:networkinterfaces` * `cloud:azure:network:networkwatchers:connectionmonitors` * `cloud:azure:network:networkwatchers:connectionmonitors:previe` * `cloud:azure:network:privatednszones` * `cloud:azure:network:publicipaddresses` * `cloud:azure:notificationhubs:namespaces:notificationhubs` * `cloud:azure:postgresql:flexibleservers` * `cloud:azure:postgresql:server` * `cloud:azure:postgresql:serverv2` * `cloud:azure:powerbidedicated:capacities` * `cloud:azure:recoveryservices:vaults` * `cloud:azure:relay:namespaces` * `cloud:azure:search:searchservices` * `cloud:azure:servicefabricmesh:applications` * `cloud:azure:signalrservice:signalr` * `cloud:azure:sql:managed` * `cloud:azure:sql:servers` * `cloud:azure:sql:servers:databases:datawarehouse` * `cloud:azure:sql:servers:databases:dtu` * `cloud:azure:sql:servers:databases:hyperscale` * `cloud:azure:sql:servers:databases:vcore` * `cloud:azure:sql:servers:elasticpools:dtu` * `cloud:azure:sql:servers:elasticpools:vcore` * `cloud:azure:storage:storageaccounts` * `cloud:azure:storage:storageaccounts:blob` * `cloud:azure:storage:storageaccounts:file` * `cloud:azure:storage:storageaccounts:queue` * `cloud:azure:storage:storageaccounts:table` * `cloud:azure:storagesync:storagesyncservices` * `cloud:azure:streamanalytics:streamingjobs` * `cloud:azure:synapse:workspaces` * `cloud:azure:synapse:workspaces:bigdatapools` * `cloud:azure:synapse:workspaces:sqlpools` * `cloud:azure:timeseriesinsights:environments` * `cloud:azure:timeseriesinsights:eventsources` * `cloud:azure:traffic_manager_profile` * `cloud:azure:virtual_network_gateway` * `cloud:azure:web:appslots` * `cloud:azure:web:functionslots` * `cloud:azure:web:hostingenvironments:v2` * `cloud:azure:web:serverfarms` * `cloud:gcp:autoscaler` * `cloud:gcp:bigquery_biengine_model` * `cloud:gcp:cloud_function` * `cloud:gcp:cloud_run_revision` * `cloud:gcp:cloudsql_database` * `cloud:gcp:filestore_instance` * `cloud:gcp:gae_app` * `cloud:gcp:gce_instance` * `cloud:gcp:gcs_bucket` * `cloud:gcp:https_lb` * `cloud:gcp:instance_group` * `cloud:gcp:internal_http_lb_rule` * `cloud:gcp:internal_network_lb_rule` * `cloud:gcp:k8s_cluster` * `cloud:gcp:k8s_container` * `cloud:gcp:k8s_node` * `cloud:gcp:k8s_pod` * `cloud:gcp:network_lb_rule` * `cloud:gcp:project` * `cloud:gcp:pubsub_snapshot` * `cloud:gcp:pubsub_subscription` * `cloud:gcp:pubsub_topic` * `cloud:gcp:pubsublite_subscription_partition` * `cloud:gcp:pubsublite_topic_partition` * `cloud:gcp:tcp_ssl_proxy_rule` * `cloud:gcp:tpu_worker` * `os:service` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") EntityProblemSLOTeam Default mappingYAML ``` - kind: entity selector: query: 'true' entityFields: firstSeenTms,lastSeenTms,tags entityTypes: - APPLICATION - SERVICE port: entity: mappings: identifier: .entityId title: .displayName blueprint: '"dynatraceEntity"' properties: firstSeen: (.firstSeenTms // 0) / 1000 | todate lastSeen: (.lastSeenTms // 0) / 1000 | todate type: .type tags: '[(.tags // [])[].stringRepresentation]' ``` Sample payloadJSON ``` { "displayName": "my host", "entityId": "HOST-06F288EE2A930951", "firstSeenTms": 1574697667547, "fromRelationships": { "isInstanceOf": [ { "id": "HOST_GROUP-0E489369D663A4BF", "type": "HOST_GROUP" } ] }, "icon": { "customIconPath": "host", "primaryIconType": "linux", "secondaryIconType": "microsoft-azure-signet" }, "lastSeenTms": 1588242361417, "managementZones": [ { "id": "6239538939987181652", "name": "main app" } ], "properties": { "bitness": 64, "cpuCores": 8, "monitoringMode": "FULL_STACK", "networkZoneId": "aws.us.east01", "osArchitecture": "X86", "osType": "LINUX" }, "tags": [ { "context": "CONTEXTLESS", "key": "architecture", "stringRepresentation": "architecture:x86", "value": "x86" }, { "context": "ENVIRONMENT", "key": "Infrastructure", "stringRepresentation": "[ENVIRONMENT]Infrastructure:Linux", "value": "Linux" } ], "toRelationships": { "isDiskOf": [ { "id": "DISK-0393340DCA3853B0", "type": "DISK" } ] }, "type": "HOST" } ``` Port entityJSON ``` { "identifier": "HOST-06F288EE2A930951", "title": "my host", "blueprint": "dynatraceEntity", "team": [], "icon": "Dynatrace", "properties": { "firstSeen": "2019-11-25T14:14:27Z", "lastSeen": "2020-04-30T14:52:41Z", "type": "HOST", "tags": ["architecture:x86", "[ENVIRONMENT]Infrastructure:Linux"] }, "relations": {}, "createdAt": "2024-2-6T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-2-6T11:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `entityTypes` | `array` | List of Dynatrace entity types to fetch. See [Dynatrace Entity Types API](https://docs.dynatrace.com/docs/dynatrace-api/environment-api/entity-v2/get-all-entity-types) for all available types. Default: `["APPLICATION","SERVICE"]`. | | `entityFields` | `string` | List of fields to include in each entity | Default mappingYAML ``` - kind: problem selector: query: 'true' port: entity: mappings: identifier: .problemId title: .title blueprint: '"dynatraceProblem"' properties: entityTags: .entityTags[].stringRepresentation evidenceDetails: .evidenceDetails.details // [] | .[].displayName managementZones: .managementZones[].name problemFilters: .problemFilters[].name severityLevel: .severityLevel status: .status startTime: .startTime / 1000 | todate endTime: .endTime | if . == -1 then null else (./1000 | todate) end relations: impactedEntities: .impactedEntities[].entityId.id linkedProblemInfo: .linkedProblemInfo.problemId rootCauseEntity: .rootCauseEntity.entityId.id ``` Sample payloadJSON ``` { "affectedEntities": [ { "entityId": { "id": "string", "type": "string" }, "name": "string" } ], "displayId": "string", "endTime": 1574697669865, "entityTags": [ { "context": "CONTEXTLESS", "key": "architecture", "stringRepresentation": "architecture:x86", "value": "x86" }, { "context": "ENVIRONMENT", "key": "Infrastructure", "stringRepresentation": "[ENVIRONMENT]Infrastructure:Linux", "value": "Linux" } ], "evidenceDetails": { "details": [ { "displayName": "Availability evidence", "entity": {}, "evidenceType": "AVAILABILITY_EVIDENCE", "groupingEntity": {}, "rootCauseRelevant": true, "startTime": 1 }, { "displayName": "User action evidence", "entity": {}, "evidenceType": "USER_ACTION_EVIDENCE", "groupingEntity": {}, "rootCauseRelevant": true, "startTime": 1 } ], "totalCount": 1 }, "impactAnalysis": { "impacts": [ { "estimatedAffectedUsers": 1, "impactType": "APPLICATION", "impactedEntity": {} } ] }, "impactLevel": "APPLICATION", "impactedEntities": [{}], "linkedProblemInfo": { "displayId": "string", "problemId": "string" }, "managementZones": [ { "id": "string", "name": "HOST" } ], "problemFilters": [ { "id": "E2A930951", "name": "BASELINE" } ], "problemId": "06F288EE2A930951", "recentComments": { "comments": [ { "authorName": "string", "content": "string", "context": "string", "createdAtTimestamp": 1, "id": "string" } ], "nextPageKey": "AQAAABQBAAAABQ==", "pageSize": 1, "totalCount": 1 }, "rootCauseEntity": {}, "severityLevel": "AVAILABILITY", "startTime": 1574697667547, "status": "CLOSED", "title": "title" } ``` Port entityJSON ``` { "identifier": "06F288EE2A930951", "title": "title", "blueprint": "dynatraceProblem", "team": [], "icon": "Dynatrace", "properties": { "entityTags": ["architecture:x86", "[ENVIRONMENT]Infrastructure:Linux"], "evidenceDetails": ["Availability evidence", "User action evidence"], "managementZones": ["HOST"], "problemFilters": ["BASELINE"], "severityLevel": "AVAILABILITY", "status": "CLOSED", "startTime": "2019-11-25T14:14:27Z", "endTime": "2020-04-30T14:52:41Z" }, "relations": { "impactedEntities": ["HOST-06F288EE2A930951"], "linkedProblemInfo": "06F288EE2A930951", "rootCauseEntity": "HOST-06F288EE2A930951" }, "createdAt": "2024-2-6T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-2-6T11:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: slo selector: query: 'true' attachRelatedEntities: true port: entity: mappings: identifier: .id title: .name blueprint: '"dynatraceSlo"' properties: status: .status target: .target enabled: .enabled warning: .warning error: .error errorBudget: .errorBudget evaluatedPercentage: .evaluatedPercentage evaluationType: .evaluationType filter: .filter relations: entities: if .__entities != null then .__entities | map(.entityId) else [] end ``` Sample payloadJSON ``` { "burnRateMetricKey": "func:slo.errorBudgetBurnRate.payment_service_availability", "denominatorValue": 90, "description": "Rate of successful payments per week", "enabled": true, "error": "NONE", "errorBudget": 1.25, "errorBudgetBurnRate": { "burnRateType": "SLOW", "burnRateValue": 1.25, "burnRateVisualizationEnabled": true, "estimatedTimeToConsumeErrorBudget": 24, "fastBurnThreshold": 1.5, "sloValue": 95 }, "errorBudgetMetricKey": "func:slo.errorBudget.payment_service_availability", "evaluatedPercentage": 96.25, "evaluationType": "AGGREGATE", "filter": "type(\"SERVICE\")", "id": "123e4567-e89b-42d3-a456-556642440000", "metricDenominator": "builtin:service.requestCount.server", "metricExpression": "(100)*(builtin:service.errors.server.successCount:splitBy())/(builtin:service.requestCount.server:splitBy())", "metricKey": "func:slo.payment_service_availability", "metricNumerator": "builtin:service.errors.server.successCount", "metricRate": "builtin:service.successes.server.rate", "name": "Payment service availability", "normalizedErrorBudgetMetricKey": "func:slo.normalizedErrorBudget.payment_service_availability", "numeratorValue": 80, "problemFilters": "[type(\"SERVICE\")]", "relatedOpenProblems": 1, "relatedTotalProblems": 1, "status": "WARNING", "target": 95, "timeframe": "-1d", "useRateMetric": true, "warning": 97.5, "__entities": [{"entityId": "SERVICE-EF25D598399706BD", "type": "SERVICE", "displayName": "oidic-authentication-service"}] } ``` Port entityJSON ``` { "identifier": "123e4567-e89b-42d3-a456-556642440000", "title": "Payment service availability", "blueprint": "dynatraceSlo", "team": [], "icon": "Dynatrace", "properties": { "status": "WARNING", "target": 95, "enabled": true, "warning": 97.5, "error": "NONE", "errorBudget": 1.25, "evaluatedPercentage": 96.25, "evaluationType": "AGGREGATE", "filter": "type(\"SERVICE\")" }, "relations": { "entities": [ "SERVICE-EF25D598399706BD" ] } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ----------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `attachRelatedEntities` | `boolean` | Whether to attach related entities to SLO. The default is false Default: `false`. | Default mappingYAML ``` - kind: team selector: query: 'true' port: entity: mappings: identifier: .value.identifier title: .value.name blueprint: '"dynatraceTeam"' properties: description: .value.descriptions links: '[.value.links[] | .url]' emailContact: .value.contactDetails[] | select(.integrationType == "EMAIL") | .email productivityToolsContact: '[.value.contactDetails[] | select(.integrationType != "EMAIL" and .url != null) | .url]' responsibilities: .value.responsibilities additionalDetails: .value.additionalInformation ``` Sample payloadJSON ``` { "objectId": "vu9U3hXa3q0AAAABABdidWlsdGluOm93bmVyc2hpcC50ZWFtcwAGdGVuYW50AAZ0ZW5hbnQAJDBkNGU5YjRmLWU3YzktM2Q2Ni1iYTlmLWUyNjRiMjhmZjI4ML7vVN4V2t6t", "value": { "name": "integration-devs", "description": "Task to improve all integrations in Port", "identifier": "integration-devs", "supplementaryIdentifiers": [ { "supplementaryIdentifier": "integral" } ], "responsibilities": { "development": false, "security": true, "operations": false, "infrastructure": false, "lineOfBusiness": false }, "contactDetails": [ { "integrationType": "MS_TEAMS", "msTeams": "internal developer portal", "url": "https://microsoft.teams.com" }, { "integrationType": "JIRA", "jira": { "project": "DEMO", "defaultAssignee": "support@devex.io" }, "url": "https://mydomain.atlassian.net/jira/your-work" }, { "integrationType": "EMAIL", "email": "support@port.io" }, { "integrationType": "SLACK", "slackChannel": "devex", "url": "https://app.slack.com/client/SOMEID" } ], "links": [ { "linkType": "REPOSITORY", "url": "https://github.com/getport" }, { "linkType": "URL", "url": "https://myjira.io/teams/integration_bugs" } ], "additionalInformation": [ { "key": "hero", "value": "batman", "url": "https://example.com" } ] } } ``` Port entityJSON ``` { "identifier": "integration-devs", "title": "integration-devs", "blueprint": "dynatraceTeam", "properties": { "description": "Task to improve all integrations in Port", "links": [ "https://github.com/getport", "https://myapp.io/teams/integration_bugs" ], "emailContact": "support@devex.io", "additionalDetails": [ { "key": "hero", "value": "batman", "url": "https://example.com" } ], "productivityContactDetails": [ "https://microsoft.teams.com", "https://mydomain.atlassian.net/jira/your-work", "https://app.slack.com/client/SOMEID" ], "responsibilities": { "development": false, "security": true, "operations": false, "infrastructure": false, "lineOfBusiness": false } } } ``` ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Configure real-time updates[​](#configure-real-time-updates "Direct link to Configure real-time updates") Currently, the Dynatrace API lacks support for programmatic webhook creation. To set up a webhook configuration in Dynatrace for sending alert notifications to the Ocean integration, follow these steps: Webhook configuration Prepare a webhook `URL` using this format: `{app_host}/integration/webhook/problem`. The `app_host` parameter should match the ingress or external load balancer where the integration will be deployed. For example, if your ingress or load balancer exposes the Dynatrace Ocean integration at `https://myservice.domain.com`, your webhook `URL` should be `https://myservice.domain.com/integration/webhook/problem`. 1. Go to Dynatrace. 2. Go to **Settings** > **Integration** > **Problem notifications**. 3. Select **Add notification**. 4. Select **Custom integration** from the available notification types. 5. Configure the notification using the following details. 1. `Enabled` - ensure the notification is enabled. 2. `Display name` - use a meaningful name such as Port Ocean Webhook. 3. `Webhook URL` - enter the value of the `URL` you created above. 4. Enable **Call webhook is new events merge into existing problems**. 5. `Custom payload` - paste the following configuration: ``` { "State":"{State}", "ProblemID":"{ProblemID}", "ProblemTitle":"{ProblemTitle}" } ``` You can customize to your taste, the only important thing is the `ProblemID` key. The webhook integration will not work without it. 6. `Alerting profile` - select the corresponding alerting profile. 7. Leave the rest of the fields as is. 6. Click **Save changes**. ## Alternative installation via webhook[​](#alternative-installation-via-webhook "Direct link to Alternative installation via webhook") While the Ocean integration described above is the recommended installation method, you may prefer to use a webhook to ingest problem from Dynatrace. If so, use the following instructions: **Note** that when using this method, data will be ingested into Port only when the webhook is triggered. **Webhook installation (click to expand)** ## Port configuration Create the following blueprint definitions: **Dynatrace microservice blueprint (click to expand)** Create in Port ``` { "identifier": "microservice", "title": "Microservice", "icon": "Service", "schema": { "properties": { "description": { "title": "Description", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Dynatrace problem blueprint (click to expand)** Create in Port ``` { "identifier": "dynatraceProblem", "description": "This blueprint represents a Dynatrace problem in our software catalog", "title": "Dynatrace Problem", "icon": "Deployment", "schema": { "properties": { "state": { "type": "string", "title": "Problem State" }, "url": { "type": "string", "format": "url", "title": "Problem URL" }, "details": { "type": "string", "title": "Details" }, "impact": { "type": "string", "title": "Problem Impact" }, "severity": { "type": "string", "title": "Problem Severity" }, "tags": { "type": "array", "items": { "type": "string" }, "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "microservice": { "title": "Impacted Services", "target": "microservice", "required": false, "many": true } } } ``` Create the following webhook configuration [using Port's UI](/context-lake/ingestion/ingest-data-into-port/webhook/ingest-via-webhooks-overview/.md?operation=ui#configuring-webhook-endpoints): **Dynatrace problem webhook configuration (click to expand)** 1. **Basic details** tab - fill the following details: 1. Title : `Dynatrace Problem Mapper`. 2. Identifier : `dynatrace_problem_mapper`. 3. Description : `A webhook configuration for problem events from Dynatrace`. 4. Icon : `Dynatrace`. 2. **Integration configuration** tab - fill the following JQ mapping: ``` [ { "blueprint": "dynatraceProblem", "entity": { "identifier": ".body.PID | tostring", "title": ".body.ProblemTitle", "properties": { "state": ".body.State", "url": ".body.ProblemURL", "details": ".body.ProblemDetailsText", "impact": ".body.ProblemImpact", "severity": ".body.ProblemSeverity", "tags": ".body.ProblemTags | split(\", \")" }, "relations": { "microservice": "[.body.ImpactedEntities[].entity]" } } } ] ``` 3. Click **Save** at the bottom of the page. Match Dynatrace entity names The webhook configuration's relation mapping will function properly only when the identifiers of the Port microservice entities match the names of the entities in your Dynatrace. If there is a mismatch, you can utilize [Dynatrace Tags](https://www.dynatrace.com/support/help/manage/tags-and-metadata) to align the actual identifier in Port. To do this, create a tag with the key `proj` and value `microservice_identifier`. Then, update the relation JQ syntax to establish a connection between the Dynatrace problem and the Port microservice. Here is the updated JQ Mappings: ``` { "blueprint": "dynatraceProblem", "entity": { ...Properties mappings, "relations": { "microservice": ".body.ProblemTags | split(\", \") | map(select(test(\"proj:\")) | sub(\"proj:\";\"\"))" } } } ``` Details **JQ expression explained (click to expand)** The above JQ expression will split the tags by comma and space, then filter the tags that start with `proj:` and remove the `proj:` prefix from the tag value. ## Create a webhook in Dynatrace 1. Log in to Dynatrace with your credentials. 2. Click on **Settings** at the left sidebar of the page. 3. Choose **Integration** and click on **Problem notifications**. 4. Select **Add notification**. 5. Select **Custom integration** from the available integration types. 6. Input the following details: 1. `Display name` - use a meaningful name such as Port Webhook. 2. `Webhook URL` - enter the value of the `url` key you received after creating the webhook configuration. 3. `Overview` - you can add an optional HTTP header to your webhook request. 4. `Custom payload` - When a problem is detected or resolved on your entity, this payload will be sent to the webhook URL. You can enter this JSON placeholder in the textbox: ``` { "State":"{State}", "PID":"{PID}", "ProblemTitle":"{ProblemTitle}", "ImpactedEntity": "{ImpactedEntity}", "ProblemDetailsText": "{ProblemDetailsText}", "ProblemImpact": "{ProblemImpact}", "ProblemSeverity": "{ProblemSeverity}", "ProblemURL": "{ProblemURL}", "ProblemTags": "{ProblemTags}", "ImpactedEntities": {ImpactedEntities} } ``` 5. `Alerting profile` - configure your preferred alerting rule or use the default one. 7. Click **Save changes** at the bottom of the page. Dynatrace webhook events To view the different payloads and events available in Dynatrace webhooks, [look here](https://www.dynatrace.com/support/help/observe-and-explore/notifications-and-alerting/problem-notifications/webhook-integration). Done! Any problem detected on your Dynatrace entity will trigger a webhook event. Port will parse the events according to the mapping and update the catalog entities accordingly. ## Let's Test It This section includes a sample response data from Dynatrace. In addition, it includes the entity created from the resync event based on the Ocean configuration provided in the previous section. ### Payload Here is an example of the payload structure from Dynatrace: **Problem response data (click to expand)** ``` { "affectedEntities": [ { "entityId": { "id": "string", "type": "string" }, "name": "string" } ], "displayId": "string", "endTime": 1574697669865, "entityTags": [ { "context": "CONTEXTLESS", "key": "architecture", "stringRepresentation": "architecture:x86", "value": "x86" }, { "context": "ENVIRONMENT", "key": "Infrastructure", "stringRepresentation": "[ENVIRONMENT]Infrastructure:Linux", "value": "Linux" } ], "evidenceDetails": { "details": [ { "displayName": "Availability evidence", "entity": {}, "evidenceType": "AVAILABILITY_EVIDENCE", "groupingEntity": {}, "rootCauseRelevant": true, "startTime": 1 }, { "displayName": "User action evidence", "entity": {}, "evidenceType": "USER_ACTION_EVIDENCE", "groupingEntity": {}, "rootCauseRelevant": true, "startTime": 1 } ], "totalCount": 1 }, "impactAnalysis": { "impacts": [ { "estimatedAffectedUsers": 1, "impactType": "APPLICATION", "impactedEntity": {} } ] }, "impactLevel": "APPLICATION", "impactedEntities": [{}], "linkedProblemInfo": { "displayId": "string", "problemId": "string" }, "managementZones": [ { "id": "string", "name": "HOST" } ], "problemFilters": [ { "id": "E2A930951", "name": "BASELINE" } ], "problemId": "06F288EE2A930951", "recentComments": { "comments": [ { "authorName": "string", "content": "string", "context": "string", "createdAtTimestamp": 1, "id": "string" } ], "nextPageKey": "AQAAABQBAAAABQ==", "pageSize": 1, "totalCount": 1 }, "rootCauseEntity": {}, "severityLevel": "AVAILABILITY", "startTime": 1574697667547, "status": "CLOSED", "title": "title" } ``` ### Mapping Result The combination of the sample payload and the Ocean configuration generates the following Port entity: **Problem entity in Port (click to expand)** ``` { "identifier": "06F288EE2A930951", "title": "title", "blueprint": "dynatraceProblem", "team": [], "icon": "Dynatrace", "properties": { "entityTags": ["architecture:x86", "[ENVIRONMENT]Infrastructure:Linux"], "evidenceDetails": ["Availability evidence", "User action evidence"], "managementZones": ["HOST"], "problemFilters": ["BASELINE"], "severityLevel": "AVAILABILITY", "status": "CLOSED", "startTime": "2019-11-25T14:14:27Z", "endTime": "2020-04-30T14:52:41Z" }, "relations": { "impactedEntities": ["HOST-06F288EE2A930951"], "linkedProblemInfo": "06F288EE2A930951", "rootCauseEntity": "HOST-06F288EE2A930951" }, "createdAt": "2024-2-6T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-2-6T11:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` ## Ingest Dynatrace Entities In this example, you will create a `dynatrace_entity` blueprint that ingests monitored entities from your Dynatrace account. You will then add a Python script to make API calls to Dynatrace REST API and fetch data for your account. * [Code Repository Example](https://github.com/port-labs/example-dynatrace-entities) --- # New Relic ![NewRelic icon](/img/guides/icons/NewRelic.svg)![NewRelic icon](/img/guides/icons/NewRelic.svg) Loading version... Port's New Relic integration allows you to model New Relic resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[newRelicService](https://docs.newrelic.com/docs/new-relic-solutions/new-relic-one/core-concepts/what-entity-new-relic/)[newRelicAlert](https://docs.newrelic.com/docs/alerts-applied-intelligence/new-relic-alerts/get-started/alerts-ai-overview-page/#issues)[newRelicServiceLevel](https://docs.newrelic.com/docs/service-level-management/consume-slm/)[Alert Condition](https://docs.newrelic.com/docs/alerts/scale-automate/rest-api/rest-api-calls-alerts/)[entity](https://docs.newrelic.com/docs/new-relic-solutions/new-relic-one/core-concepts/what-entity-new-relic/)[entities](https://docs.newrelic.com/docs/new-relic-solutions/new-relic-one/core-concepts/what-entity-new-relic/) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Additional Configuration[​](#additional-configuration "Direct link to Additional Configuration") * **newRelicTypes** - An array of Newrelic entity types that will be fetched. The default value is \['SERVICE', 'APPLICATION']. This is related to the type field in the Newrelic entity. * **calculateOpenIssueCount:** * A boolean value that indicates if the integration should calculate the number of open issues for each entity. The default value is \`false\`\`. * **NOTE** - This can cause a performance degradation as the integration will have to calculate the number of open issues for each entity, which unfortunately is not supported by the New Relic API. * **entityQueryFilter:** * A filter that will be applied to the New Relic API query. This will be placed inside the `query` field of the `entitySearch` query in the New Relic GraphQL API. For examples of query filters [click here](https://docs.newrelic.com/docs/apis/nerdgraph/examples/nerdgraph-entities-api-tutorial/#search-query). * Not specifying this field will cause the integration to fetch all the entities and map them to the blueprint defined in the `kind`. * Rule of thumb - Most of the time the `EntityQueryFilter` will be the same as the `NewRelicTypes`. For example, if we want to fetch all the services and applications we will set the `EntityQueryFilter` to `type in ('SERVICE','APPLICATION')` and the `NewRelicTypes` to `['SERVICE', 'APPLICATION']`. * **entityExtraPropertiesQuery:** * An optional property that allows defining extra properties to fetch for each Newrelic entity. This will be concatenated with the default query properties we are requesting under the `entities` section in the `entitySearch` query in the Newrelic GraphQL API. For examples of additional query properties [click here](https://docs.newrelic.com/docs/apis/nerdgraph/examples/nerdgraph-entities-api-tutorial/#apm-summary). * The `port`, `entity` and the `mappings` keys are used to map the Newrelic object fields to Port entities. To create multiple mappings of the same kind, you can add another item in the `resources` array. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: newRelicService selector: query: 'true' newRelicTypes: - SERVICE - APPLICATION calculateOpenIssueCount: true entityQueryFilter: type in ('SERVICE','APPLICATION') entityExtraPropertiesQuery: |- ... on ApmApplicationEntityOutline { guid name apmSummary { apdexScore errorRate hostCount instanceCount responseTimeAverage throughput } } port: entity: mappings: identifier: .guid title: .name blueprint: '"newRelicService"' properties: has_apm: if .domain | contains("APM") then "true" else "false" end link: .permalink open_issues_count: .open_issues_count reporting: .reporting tags: .tags type: .type throughput: .apmSummary.throughput error_rate: .apmSummary.errorRate response_time_avg: .apmSummary.responseTimeAverage instance_count: .apmSummary.instanceCount apdex: .apmSummary.apdexScore - kind: newRelicAlert selector: query: .state == "ACTIVATED" or .state == "CREATED" newRelicTypes: - ISSUE port: entity: mappings: identifier: .issueId title: .title[0] blueprint: '"newRelicAlert"' properties: priority: .priority state: .state sources: .sources conditionName: .conditionName alertPolicyNames: .policyName activatedAt: if .activatedAt == null then null else .activatedAt / 1000 | todate end link: '"https://one.newrelic.com/launcher/nrai.launcher?pane=" + ("{\"isPhoton\": true, \"id\": \"\(.issueId)\", \"nerdletId\": \"nrai.issue-redirect\"}" | @base64)' description: .description relations: alert_to_workload: .__APPLICATION.entity_guids + .__SERVICE.entity_guids cloud_resource: combinator: '"and"' rules: - property: '"guid"' operator: '"in"' value: .entityGuids - kind: newRelicServiceLevel selector: query: 'true' port: entity: mappings: identifier: .serviceLevel.indicators[0].id title: .serviceLevel.indicators[0].name blueprint: '"newRelicServiceLevel"' properties: description: .serviceLevel.indicators[0].description targetThreshold: .serviceLevel.indicators[0].objectives[0].target createdAt: if .serviceLevel.indicators[0].createdAt != null then (.serviceLevel.indicators[0].createdAt | tonumber / 1000 | todate) else null end updatedAt: if .serviceLevel.indicators[0].updatedAt != null then (.serviceLevel.indicators[0].updatedAt | tonumber / 1000 | todate) else null end createdBy: .serviceLevel.indicators[0].createdBy.email sli: .__SLI.SLI tags: .tags slo_compliance: .__SLI.SLI >= .serviceLevel.indicators[0].objectives[0].target relations: workload: .tags."nr.associatedEntityGuid"[0] - kind: entity selector: query: 'true' entityQueryFilter: >- type IN ( 'AWSEC2INSTANCE', 'AWSS3BUCKET', 'AWSRDSDBINSTANCE', 'AWSLAMBDAFUNCTION', 'AWSELBLOADBALANCER', 'AZUREVIRTUALMACHINE', 'AZURESQLDATABASE', 'GCPCOMPUTEINSTANCE', 'GCPSTORAGEBUCKET', 'GCPSQLDATABASEINSTANCE' ) port: entity: mappings: identifier: .guid title: .name blueprint: '"newRelicCloudResource"' properties: infrastructureIntegrationType: .type reporting: .reporting link: .permalink tags: .tags - kind: entities selector: query: 'true' entityQueryFilter: type IN ( 'DASHBOARD' ) port: entity: mappings: identifier: .guid title: .name blueprint: '"newRelicDashboards"' properties: dashboard_link: .permalink - kind: newRelicService selector: query: 'true' newRelicTypes: - SERVICE - APPLICATION entityQueryFilter: type in ('SERVICE','APPLICATION') port: entity: mappings: identifier: .guid title: .name blueprint: '"workload"' relations: new_relic_workload: .guid ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. newRelicServicenewRelicAlertnewRelicServiceLevelentityentities Default mappingYAML ``` - kind: newRelicService selector: query: 'true' newRelicTypes: - SERVICE - APPLICATION calculateOpenIssueCount: true entityQueryFilter: type in ('SERVICE','APPLICATION') entityExtraPropertiesQuery: |- ... on ApmApplicationEntityOutline { guid name apmSummary { apdexScore errorRate hostCount instanceCount responseTimeAverage throughput } } port: entity: mappings: identifier: .guid title: .name blueprint: '"newRelicService"' properties: has_apm: if .domain | contains("APM") then "true" else "false" end link: .permalink open_issues_count: .open_issues_count reporting: .reporting tags: .tags type: .type throughput: .apmSummary.throughput error_rate: .apmSummary.errorRate response_time_avg: .apmSummary.responseTimeAverage instance_count: .apmSummary.instanceCount apdex: .apmSummary.apdexScore - kind: newRelicService selector: query: 'true' newRelicTypes: - SERVICE - APPLICATION entityQueryFilter: type in ('SERVICE','APPLICATION') port: entity: mappings: identifier: .guid title: .name blueprint: '"workload"' relations: new_relic_workload: .guid ``` Sample payloadJSON ``` { "accountId": 4444532, "alertSeverity": "NOT_CONFIGURED", "domain": "INFRA", "entityType": "INFRASTRUCTURE_HOST_ENTITY", "guid": "MTIzNDU2Nzg5fElORlJBfE5BfDY1MjQwNDc0NjE4MzUyMDkwOTU=", "lastReportingChangeAt": 1715351571254, "name": "UserMacbook", "permalink": "https://one.eu.newrelic.com/redirect/entity/MTIzNDU2Nzg5fElORlJBfE5BfDY1MjQwNDc0NjE4MzUyMDkwOTU=", "reporting": true, "tags": [ { "key": "account", "values": [ "Account 4444831" ] }, { "key": "accountId", "values": [ "4444831" ] }, { "key": "agentName", "values": [ "Infrastructure" ] }, { "key": "agentVersion", "values": [ "1.50.0" ] }, { "key": "coreCount", "values": [ "8" ] }, { "key": "fullHostname", "values": [ "usermacbook" ] }, { "key": "hostStatus", "values": [ "running" ] }, { "key": "hostname", "values": [ "Usermacbook" ] }, { "key": "instanceType", "values": [ "MacBook Air MacBookAir10,1" ] }, { "key": "kernelVersion", "values": [ "23.2.0" ] }, { "key": "linuxDistribution", "values": [ "macOS 14.2.1" ] }, { "key": "operatingSystem", "values": [ "macOS" ] }, { "key": "processorCount", "values": [ "8" ] }, { "key": "systemMemoryBytes", "values": [ "17179869184" ] }, { "key": "trustedAccountId", "values": [ "4444532" ] } ], "type": "HOST" } ``` Port entityJSON ``` { "identifier": "MTIzNDU2Nzg5fElORlJBfE5BfDY1MjQwNDc0NjE4MzUyMDkwOTU=", "title": "UserMacbook", "blueprint": "newRelicAlert", "team": [], "icon": "NewRelic", "properties": { "has_apm": false, "link": "https://one.eu.newrelic.com/redirect/entity/MTIzNDU2Nzg5fElORlJBfE5BfDY1MjQwNDc0NjE4MzUyMDkwOTU=", "open_issues_count": null, "reporting": true, "tags": [ { "key": "account", "values": [ "Account 4444831" ] }, { "key": "accountId", "values": [ "4444831" ] }, { "key": "agentName", "values": [ "Infrastructure" ] }, { "key": "agentVersion", "values": [ "1.50.0" ] }, { "key": "coreCount", "values": [ "8" ] }, { "key": "fullHostname", "values": [ "usermacbook" ] }, { "key": "hostStatus", "values": [ "running" ] }, { "key": "hostname", "values": [ "Usermacbook" ] }, { "key": "instanceType", "values": [ "MacBook Air MacBookAir10,1" ] }, { "key": "kernelVersion", "values": [ "23.2.0" ] }, { "key": "linuxDistribution", "values": [ "macOS 14.2.1" ] }, { "key": "operatingSystem", "values": [ "macOS" ] }, { "key": "processorCount", "values": [ "8" ] }, { "key": "systemMemoryBytes", "values": [ "17179869184" ] }, { "key": "trustedAccountId", "values": [ "4444532" ] } ], "domain": "INFRA", "type": "HOST" }, "relations": {}, "createdAt": "2024-2-6T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-2-6T11:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: newRelicAlert selector: query: .state == "ACTIVATED" or .state == "CREATED" newRelicTypes: - ISSUE port: entity: mappings: identifier: .issueId title: .title[0] blueprint: '"newRelicAlert"' properties: priority: .priority state: .state sources: .sources conditionName: .conditionName alertPolicyNames: .policyName activatedAt: if .activatedAt == null then null else .activatedAt / 1000 | todate end link: '"https://one.newrelic.com/launcher/nrai.launcher?pane=" + ("{\"isPhoton\": true, \"id\": \"\(.issueId)\", \"nerdletId\": \"nrai.issue-redirect\"}" | @base64)' description: .description relations: alert_to_workload: .__APPLICATION.entity_guids + .__SERVICE.entity_guids cloud_resource: combinator: '"and"' rules: - property: '"guid"' operator: '"in"' value: .entityGuids ``` Sample payloadJSON ``` { "issueId": "MjQwNzIwN3xBUE18QVBQTElDQVRJT058MjIwMzEwNzV8MTA0NzYwNzA5", "title": "My Issue", "priority": "CRITICAL", "state": "ACTIVATED", "sources": ["My Source"], "conditionName": ["My Condition"], "policyName": ["My Policy"], "activatedAt": "2022-01-01T00:00:00Z" } ``` Port entityJSON ``` { "identifier": "My Issue", "title": "My Issue", "blueprint": "newRelicAlert", "team": [], "icon": "NewRelic", "properties": { "priority": "CRITICAL", "state": "ACTIVATED", "sources": ["My Source"], "conditionName": ["My Condition"], "alertPolicyNames": ["My Policy"], "activatedAt": "2022-01-01T00:00:00Z" }, "relations": { "newRelicService": "My Service" }, "createdAt": "2024-2-6T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-2-6T11:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: newRelicServiceLevel selector: query: 'true' port: entity: mappings: identifier: .serviceLevel.indicators[0].id title: .serviceLevel.indicators[0].name blueprint: '"newRelicServiceLevel"' properties: description: .serviceLevel.indicators[0].description targetThreshold: .serviceLevel.indicators[0].objectives[0].target createdAt: if .serviceLevel.indicators[0].createdAt != null then (.serviceLevel.indicators[0].createdAt | tonumber / 1000 | todate) else null end updatedAt: if .serviceLevel.indicators[0].updatedAt != null then (.serviceLevel.indicators[0].updatedAt | tonumber / 1000 | todate) else null end createdBy: .serviceLevel.indicators[0].createdBy.email sli: .__SLI.SLI tags: .tags slo_compliance: .__SLI.SLI >= .serviceLevel.indicators[0].objectives[0].target relations: workload: .tags."nr.associatedEntityGuid"[0] ``` Sample payloadJSON ``` { "serviceLevel": { "indicators": [ { "createdAt": 1721030560937, "createdBy": { "email": "user@domain.com" }, "description": "Proportion of requests that are served faster than a threshold.", "guid": "NDM2OTY4MHxFWFR8U0VSVklDRV9MRVZFTHw1OTk0MzQ", "id": "599434", "name": "Service Level Name - Metric", "objectives": [ { "description": null, "name": null, "target": 95, "timeWindow": { "rolling": { "count": 7, "unit": "DAY" } } } ], "resultQueries": { "indicator": { "nrql": "SELECT clamp_max(sum(newrelic.sli.good) / sum(newrelic.sli.valid) * 100, 100) AS 'SLI' FROM Metric WHERE entity.guid = 'NDM2OTY4MHxFWFR8U0VSVklDRV9MRVZFTHw1OTk0MzQ' UNTIL 2 minutes AGO" } }, "updatedAt": null, "updatedBy": null } ] }, "tags": { "account": [ "Account [REDACTED]" ], "accountId": [ "[REDACTED]" ], "category": [ "latency" ], "nr.associatedEntityGuid": [ "NDM2OTY4MHxBUE18QVBQTElDQVRJT058NTkxMTYyMjE0" ], "nr.associatedEntityName": [ "Service Name 01" ], "nr.associatedEntityType": [ "APM_APPLICATION" ], "nr.sliComplianceCategory": [ "Non-compliant" ], "nr.sloPeriod": [ "7d" ], "nr.sloTarget": [ "95.0%" ], "trustedAccountId": [ "[REDACTED]" ] }, "__SLI": { "SLI": 87.56 } } ``` Port entityJSON ``` { "blueprint": "newRelicServiceLevel", "identifier": "599434", "title": "Service Level Name - Metric", "icon": "NewRelic", "properties": { "description": "Proportion of requests that are served faster than a threshold.", "targetThreshold": 95, "createdAt": "2024-07-15T08:02:40Z", "updatedAt": null, "createdBy": "user@domain.com", "serviceLevelIndicator": 87.56, "tags": { "account": [ "Account [REDACTED]" ], "accountId": [ "[REDACTED]" ], "category": [ "latency" ], "nr.associatedEntityGuid": [ "NDM2OTY4MHxBUE18QVBQTElDQVRJT058NTkxMTYyMjE0" ], "nr.associatedEntityName": [ "Service Name 01" ], "nr.associatedEntityType": [ "APM_APPLICATION" ], "nr.sliComplianceCategory": [ "Non-compliant" ], "nr.sloPeriod": [ "7d" ], "nr.sloTarget": [ "95.0%" ], "trustedAccountId": [ "[REDACTED]" ] } }, "relations": { "newRelicService": "NDM2OTY4MHxFWFR8U0VSVklDRV9MRVZFTHw1OTk0MzQ" }, "createdAt": "2024-08-06T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-08-06T09:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: entity selector: query: 'true' entityQueryFilter: >- type IN ( 'AWSEC2INSTANCE', 'AWSS3BUCKET', 'AWSRDSDBINSTANCE', 'AWSLAMBDAFUNCTION', 'AWSELBLOADBALANCER', 'AZUREVIRTUALMACHINE', 'AZURESQLDATABASE', 'GCPCOMPUTEINSTANCE', 'GCPSTORAGEBUCKET', 'GCPSQLDATABASEINSTANCE' ) port: entity: mappings: identifier: .guid title: .name blueprint: '"newRelicCloudResource"' properties: infrastructureIntegrationType: .type reporting: .reporting link: .permalink tags: .tags ``` Default mappingYAML ``` - kind: entities selector: query: 'true' entityQueryFilter: type IN ( 'DASHBOARD' ) port: entity: mappings: identifier: .guid title: .name blueprint: '"newRelicDashboards"' properties: dashboard_link: .permalink ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 New Relic 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 New Relic 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If New Relic is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that New Relic can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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.webhookUsername,integration.secrets.webhookSecret` parameters secure your webhooks. If not provided, the integration will process webhooks without validating the source of the events. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Tags[​](#tags "Direct link to Tags") Some Newrelic `entities` have a property named `tags` which contains potentially useful information such as machine information, hostname, agent name & version, and more. For example: ``` "tags": [ { "key": "coreCount", "values": [ "10" ] }, { "key": "hostStatus", "values": [ "running" ] }, ] ``` Before mapping, this integration performs a transformation on each `tag`, after which the example above would look like this: ``` tags = ["coreCount":"10","hostStatus":"running"] ``` ## Examples[​](#examples "Direct link to Examples") Examples of blueprints and the relevant integration configurations: ### Service (Entity)[​](#service-entity "Direct link to Service (Entity)") **Service blueprint (click to expand)** Create in Port ``` { "identifier": "newRelicService", "description": "This blueprint represents a New Relic service or application in our software catalog", "title": "New Relic Service", "icon": "NewRelic", "schema": { "properties": { "has_apm": { "title": "Has APM", "type": "boolean" }, "open_issues_count": { "title": "Open Issues Count", "type": "number", "default": 0 }, "link": { "title": "Link", "type": "string", "format": "url" }, "reporting": { "title": "Reporting", "type": "boolean" }, "tags": { "title": "Tags", "type": "object" }, "account_id": { "title": "Account ID", "type": "string" }, "type": { "title": "Type", "type": "string" }, "domain": { "title": "Domain", "type": "string" }, "throughput": { "title": "Throughput", "type": "number" }, "response_time_avg": { "title": "Response Time AVG", "type": "number" }, "error_rate": { "title": "Error Rate", "type": "number" }, "instance_count": { "title": "Instance Count", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: newRelicService selector: query: "true" newRelicTypes: ["SERVICE", "APPLICATION"] calculateOpenIssueCount: true entityQueryFilter: "type in ('SERVICE','APPLICATION')" entityExtraPropertiesQuery: | ... on ApmApplicationEntity { guid name alertSeverity applicationId apmBrowserSummary { ajaxRequestThroughput ajaxResponseTimeAverage jsErrorRate pageLoadThroughput pageLoadTimeAverage } apmSummary { apdexScore errorRate hostCount instanceCount nonWebResponseTimeAverage nonWebThroughput responseTimeAverage throughput webResponseTimeAverage webThroughput } } port: entity: mappings: blueprint: '"newRelicService"' identifier: .guid title: .name properties: has_apm: 'if .domain | contains("APM") then "true" else "false" end' link: .permalink open_issues_count: .__open_issues_count reporting: .reporting tags: .tags domain: .domain type: .type ``` ### Issue[​](#issue "Direct link to Issue") **Issue blueprint (click to expand)** Create in Port ``` { "identifier": "newRelicAlert", "description": "This blueprint represents a New Relic alert in our software catalog", "title": "New Relic Alert", "icon": "NewRelic", "schema": { "properties": { "priority": { "type": "string", "title": "Priority", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"], "enumColors": { "CRITICAL": "red", "HIGH": "red", "MEDIUM": "yellow", "LOW": "green" } }, "state": { "type": "string", "title": "State", "enum": ["ACTIVATED", "CLOSED", "CREATED"], "enumColors": { "ACTIVATED": "yellow", "CLOSED": "green", "CREATED": "lightGray" } }, "trigger": { "type": "string", "title": "Trigger" }, "sources": { "type": "array", "title": "Sources" }, "alertPolicyNames": { "type": "array", "title": "Alert Policy Names" }, "conditionName": { "type": "array", "title": "Condition Name" }, "activatedAt": { "type": "string", "title": "Time Issue was activated" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "newRelicService": { "title": "New Relic Service", "target": "newRelicService", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: newRelicAlert selector: query: "true" newRelicTypes: ["ISSUE"] port: entity: mappings: blueprint: '"newRelicAlert"' identifier: .issueId title: .title[0] properties: priority: .priority state: .state sources: .sources conditionName: .conditionName alertPolicyNames: .policyName activatedAt: .activatedAt relations: newRelicService: .__APPLICATION.entity_guids + .__SERVICE.entity_guids ``` ### Service Level[​](#service-level "Direct link to Service Level") **Service Level blueprint (click to expand)** Create in Port ``` { "identifier": "newRelicServiceLevel", "description": "This blueprint represents a New Relic Service Level", "title": "New Relic Service Level", "icon": "NewRelic", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "targetThreshold": { "icon": "DefaultProperty", "title": "Target Threshold", "type": "number" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "createdBy": { "title": "Creator", "type": "string", "format": "user" }, "sli": { "type": "number", "title": "SLI" }, "tags": { "type": "object", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "newRelicService": { "title": "New Relic service", "target": "newRelicService", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: newRelicServiceLevel selector: query: 'true' port: entity: mappings: blueprint: '"newRelicServiceLevel"' identifier: .serviceLevel.indicators[0].id title: .serviceLevel.indicators[0].name properties: description: .serviceLevel.indicators[0].description targetThreshold: .serviceLevel.indicators[0].objectives[0].target createdAt: if .serviceLevel.indicators[0].createdAt != null then (.serviceLevel.indicators[0].createdAt | tonumber / 1000 | todate) else null end updatedAt: if .serviceLevel.indicators[0].updatedAt != null then (.serviceLevel.indicators[0].updatedAt | tonumber / 1000 | todate) else null end createdBy: .serviceLevel.indicators[0].createdBy.email sli: .__SLI.SLI tags: .tags relations: newRelicService: .serviceLevel.indicators[0].guid ``` --- # Prometheus Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's Prometheus integration allows you to ingest Prometheus monitoring and alerting data into your software catalog using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. After installing this integration, you can visualize your Prometheus alerts, alerting rules, and scrape targets using Port's dashboards. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The Prometheus integration can ingest the following resources into Port. It is possible to reference any field that appears in the API responses in the mapping configuration. For detailed API documentation, see the [Prometheus HTTP API documentation](https://prometheus.io/docs/prometheus/latest/querying/api/). * **Alerts** - Active alerts from [`/api/v1/alerts`](https://prometheus.io/docs/prometheus/latest/querying/api/#getting-alerts). * **Rules** - Alerting and recording rules from [`/api/v1/rules`](https://prometheus.io/docs/prometheus/latest/querying/api/#rules). * **Targets** - Scrape targets and their health status from [`/api/v1/targets`](https://prometheus.io/docs/prometheus/latest/querying/api/#targets). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, you need: * A Prometheus instance accessible via HTTP/HTTPS. * Network access to the Prometheus API endpoints. * If authentication is enabled, appropriate credentials (API token, username/password, or bearer token). **Prometheus authentication:** Prometheus does not require authentication by default. However, if your Prometheus instance has authentication enabled, you can use: * **Basic Authentication** - Username and password. * **Bearer Token** - API token in the Authorization header. * **No Authentication** - If authentication is disabled (default). ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. Configure: * **Identifier**: `prometheus-integration`. * **Base URL**: your Prometheus server URL (for example `http://prometheus:9090`). * **Authentication type**: `none`, `basic`, or `bearer_token`, depending on how your Prometheus instance is secured. * **Credentials**: provide username/password or token if authentication is enabled. 3. Click **Save & install**. Network access When using Hosted by Port, Port makes outbound calls to your API from [static IP addresses](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md#port-source-ip-addresses). Your Prometheus instance must be reachable from those addresses. After the integration is installed, continue to the **Configuration** section on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and your Prometheus base URL. If authentication is enabled, also replace `YOUR_PROMETHEUS_USERNAME` and `YOUR_PROMETHEUS_PASSWORD` (or use bearer token). **Without authentication:** ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-prometheus-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set createPortResourcesOrigin="Empty" \ --set scheduledResyncInterval=120 \ --set integration.identifier="prometheus-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="http://prometheus:9090" \ --set integration.config.authType="none" ``` **With basic authentication:** ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-prometheus-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set createPortResourcesOrigin="Empty" \ --set scheduledResyncInterval=120 \ --set integration.identifier="prometheus-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="http://prometheus:9090" \ --set integration.config.authType="basic" \ --set integration.config.username="YOUR_PROMETHEUS_USERNAME" \ --set integration.config.password="YOUR_PROMETHEUS_PASSWORD" ``` **With bearer token:** ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-prometheus-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set createPortResourcesOrigin="Empty" \ --set scheduledResyncInterval=120 \ --set integration.identifier="prometheus-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="http://prometheus:9090" \ --set integration.config.authType="bearer_token" \ --set integration.config.apiToken="YOUR_PROMETHEUS_BEARER_TOKEN" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters This table summarizes the available parameters for the installation. | Parameter | Description | Example | Required | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | -------- | | `port.clientId` | Your Port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.baseUrl` | Your Port API URL (`https://api.port.io` for EU, `https://api.us.port.io` for US). | | ✅ | | `integration.config.baseUrl` | Base URL of your Prometheus instance. | | ✅ | | `integration.config.authType` | Authentication type (`none`, `basic`, or `bearer_token`). | none | ✅ | | `integration.config.username` | Username for basic authentication (required if `authType` is `basic`). | | ❌ | | `integration.config.password` | Password for basic authentication (required if `authType` is `basic`). | | ❌ | | `integration.config.apiToken` | Bearer token for authentication (required if `authType` is `bearer_token`). | | ❌ | | `integration.eventListener.type` | Event listener type. See [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md). | POLLING | ✅ | | `integration.type` | Integration type (must be `custom`). | custom | ✅ | | `integration.identifier` | Unique identifier for the integration instance. | prometheus-integration | ✅ | | `scheduledResyncInterval` | Minutes between scheduled syncs. When omitted, the event listener interval is used. | 120 | ❌ | | `createPortResourcesOrigin` | Set to `Empty` to register only the integration without creating default blueprints or mappings. | Empty | ❌ | | `sendRawDataExamples` | Sends sample payloads from the API to Port for easier mapping. | true | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and your Prometheus base URL. If authentication is enabled, also replace the authentication credentials. **Without authentication:** ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__CREATE_PORT_RESOURCES_ORIGIN="Empty" \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__IDENTIFIER="prometheus-integration" \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="http://prometheus:9090" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="none" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` **With basic authentication:** ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__CREATE_PORT_RESOURCES_ORIGIN="Empty" \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__IDENTIFIER="prometheus-integration" \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="http://prometheus:9090" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="basic" \ -e OCEAN__INTEGRATION__CONFIG__USERNAME="YOUR_PROMETHEUS_USERNAME" \ -e OCEAN__INTEGRATION__CONFIG__PASSWORD="YOUR_PROMETHEUS_PASSWORD" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` **With bearer token:** ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__CREATE_PORT_RESOURCES_ORIGIN="Empty" \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__IDENTIFIER="prometheus-integration" \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="http://prometheus:9090" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="YOUR_PROMETHEUS_BEARER_TOKEN" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before the integration can sync data, you need to create the required blueprints in Port. These blueprints define the data model for your Prometheus resources. **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on the `+ Blueprint` button. 3. Copy and paste each blueprint JSON from the sections below. **Prometheus Alert Blueprint (click to expand)** Active alerts from Prometheus: Create in Port ``` { "identifier": "prometheus-alert", "title": "Prometheus Alert", "icon": "Prometheus", "schema": { "properties": { "alertname": { "title": "Alert Name", "type": "string" }, "state": { "title": "State", "type": "string" }, "severity": { "title": "Severity", "type": "string" }, "summary": { "title": "Summary", "type": "string" }, "description": { "title": "Description", "type": "string" }, "activeAt": { "title": "Active At", "type": "string", "format": "date-time" }, "labels": { "title": "Labels", "type": "object" }, "annotations": { "title": "Annotations", "type": "object" }, "value": { "title": "Value", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Prometheus Rule Blueprint (click to expand)** Alerting and recording rules: Create in Port ``` { "identifier": "prometheus-rule", "title": "Prometheus Rule", "icon": "Prometheus", "schema": { "properties": { "name": { "title": "Name", "type": "string" }, "type": { "title": "Type", "type": "string" }, "query": { "title": "Query", "type": "string" }, "duration": { "title": "Duration", "type": "string" }, "labels": { "title": "Labels", "type": "object" }, "annotations": { "title": "Annotations", "type": "object" }, "health": { "title": "Health", "type": "string" }, "lastError": { "title": "Last Error", "type": "string" }, "evaluationTime": { "title": "Evaluation Time", "type": "number" }, "lastEvaluation": { "title": "Last Evaluation", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Prometheus Target Blueprint (click to expand)** Scrape targets and their health status: Create in Port ``` { "identifier": "prometheus-target", "title": "Prometheus Target", "icon": "Prometheus", "schema": { "properties": { "scrapePool": { "title": "Scrape Pool", "type": "string" }, "scrapeUrl": { "title": "Scrape URL", "type": "string", "format": "url" }, "health": { "title": "Health", "type": "string" }, "lastError": { "title": "Last Error", "type": "string" }, "lastScrape": { "title": "Last Scrape", "type": "string", "format": "date-time" }, "lastScrapeDuration": { "title": "Last Scrape Duration", "type": "number" }, "labels": { "title": "Labels", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` ## Configuration[​](#configuration "Direct link to Configuration") After installation, define which endpoints to sync in your integration configuration. Each resource maps an API endpoint to Port entities using [JQ expressions](https://stedolan.github.io/jq/manual/) to transform the data. **Key mapping components:** * **`kind`**: The API endpoint path (combined with your base URL). * **`selector.query`**: JQ filter to include/exclude entities (use `'true'` to sync all). * **`selector.data_path`**: JQ expression pointing to the array of items in the response. * **`port.entity.mappings`**: How to map API fields to Port entity properties. For more details on how the no-code Ocean custom integration works, see the [How it works](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview#how-it-works) section in the custom integration overview. **Prometheus API response format:** Prometheus API responses follow this structure: ``` { "status": "success", "data": { "alerts": [ { "labels": { "alertname": "HighCPUUsage", "severity": "warning" }, "annotations": { "summary": "CPU usage is above 80%" }, "state": "firing", "activeAt": "2024-01-15T10:30:00Z", "value": "85.5" } ] } } ``` The actual data array is typically nested under `.data.alerts`, `.data.groups` (for rules), or `.data.activeTargets` (for targets). **To configure the mappings:** 1. Go to your [connectors page](https://app.port.io/settings/data-sources). 2. Find your Prometheus integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below. **Alerts mapping (click to expand)** Active alerts from `/api/v1/alerts`: ``` resources: - kind: /api/v1/alerts selector: query: 'true' data_path: '.data.alerts' port: entity: mappings: identifier: '"\(.labels.alertname)-\(.activeAt)"' title: .labels.alertname blueprint: '"prometheus-alert"' properties: alertname: .labels.alertname state: .state severity: (.labels.severity // "unknown") summary: (.annotations.summary // "") description: (.annotations.description // "") activeAt: .activeAt labels: .labels annotations: .annotations value: (.value // "") ``` Composite identifier The identifier uses a composite of `alertname` and `activeAt` to ensure uniqueness, as the same alert can fire multiple times. This prevents alerts from overwriting each other. **Rules mapping (click to expand)** Alerting and recording rules from `/api/v1/rules`: ``` resources: - kind: /api/v1/rules selector: query: 'true' data_path: '.data.groups[].rules[]' port: entity: mappings: identifier: .name title: .name blueprint: '"prometheus-rule"' properties: name: .name type: .type query: .query duration: (.duration // "") labels: (.labels // {}) annotations: (.annotations // {}) health: .health lastError: (.lastError // "") evaluationTime: (.evaluationTime // 0) lastEvaluation: .lastEvaluation ``` Rules structure The `/api/v1/rules` endpoint returns rules grouped by rule group. The mapping uses `.data.groups[].rules[]` to flatten all rules from all groups into a single array. **Targets mapping (click to expand)** Scrape targets from `/api/v1/targets`: ``` resources: - kind: /api/v1/targets selector: query: 'true' data_path: '.data.activeTargets[]' port: entity: mappings: identifier: .scrapeUrl title: (.labels.job // .scrapeUrl) blueprint: '"prometheus-target"' properties: scrapePool: .scrapePool scrapeUrl: .scrapeUrl health: .health lastError: (.lastError // "") lastScrape: .lastScrape lastScrapeDuration: (.lastScrapeDuration // 0) labels: .labels ``` Targets structure The `/api/v1/targets` endpoint returns both `activeTargets` and `droppedTargets`. This mapping only includes active targets. You can add a separate mapping for dropped targets if needed. 5. Click `Save` to save the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to customize your setup or test different API endpoints before committing to a configuration, use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md). **The interactive builder helps you:** 1. Test your Prometheus API endpoints with live data. 2. Automatically detect the data structure and field types. 3. Generate blueprints and resource mappings tailored to your preferences. 4. Get installation commands with your configuration pre-filled. Simply provide your Prometheus API details, and the builder will generate everything you need to install and create the integration in Port. --- # Sentry ![Sentry icon](/img/guides/icons/Sentry.svg)![Sentry icon](/img/guides/icons/dark/Sentry.svg) Loading version... Port's Sentry integration allows you to model Sentry resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[User](https://docs.sentry.io/api/organizations/list-an-organizations-members/)[Team](https://docs.sentry.io/api/teams/list-an-organizations-teams/)[Project](https://docs.sentry.io/api/projects/)[Project Tag](https://docs.sentry.io/api/projects/list-a-tags-values/)[Issue](https://docs.sentry.io/api/events/list-a-projects-issues/)[Issue Tag](https://docs.sentry.io/api/events/list-a-tags-values-for-an-issue/) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A Sentry account with access to the organization you want to sync. * Your Port user role is set to `Admin`. * A Sentry API token for resyncing data into Port. **Required permissions (click to expand)** * **Issue & Event**: Read (`event:read`) - for syncing issues and issue tags. * **Member**: Read (`member:read`) - for syncing organization members and teams. * **Organization**: Read (`org:read`) - for organization-level API access. * **Project**: Read (`project:read`) - for syncing projects and project tags. * **Team**: Read (`team:read`) - for syncing teams and team members. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: user selector: query: 'true' port: entity: mappings: identifier: .email title: .user.name blueprint: '"sentryUser"' properties: username: .user.username isActive: .user.isActive dateJoined: .user.dateJoined lastLogin: .user.lastLogin orgRole: .orgRole inviteStatus: .inviteStatus - kind: user selector: query: 'true' port: entity: mappings: identifier: .email blueprint: '"_user"' relations: sentry_user: .email - kind: project-tag selector: query: 'true' tag: environment port: entity: mappings: identifier: .slug + "-" + .__tags.name title: .name + "-" + .__tags.name blueprint: '"sentryProjectEnvironment"' properties: dateCreated: .dateCreated platform: .platform status: .status link: .organization.links.organizationUrl + "/projects/" + .name - kind: issue-tag selector: query: 'true' tag: environment port: entity: mappings: identifier: .id + "-" + .item.name title: .title + " -" + " " + .item.name blueprint: '"sentryIssue"' properties: link: .permalink + "?environment=" + .item.name status: .status isUnhandled: .isUnhandled relations: projectEnvironment: (.project.slug as $slug | .item | "\($slug)-\(.name)") assignee: combinator: '"and"' rules: - operator: '"="' property: '"$identifier"' value: .assignedTo.email - kind: team selector: query: 'true' includeMembers: true port: entity: mappings: identifier: .slug title: .name blueprint: '"sentryTeam"' properties: dateCreated: .dateCreated memberCount: .memberCount roles: .teamRole projects: .projects | map (.slug) relations: members: if .__members != null then .__members | map(.user.email) | map(select(. != null)) else [] end ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Examples of blueprints and the relevant integration configurations: UserTeamProjectIssueProject Environment Default mappingYAML ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: .email title: .user.name blueprint: '"sentryUser"' properties: username: .user.username isActive: .user.isActive dateJoined: .user.dateJoined lastLogin: .user.lastLogin orgRole: .orgRole inviteStatus: .inviteStatus - kind: user selector: query: 'true' port: entity: mappings: identifier: .email blueprint: '"_user"' relations: sentry_user: .email ``` Sample payloadJSON ``` { "id": "10909027", "email": "developer@getport.io", "name": "Michael", "user": { "id": "1722098", "name": "Michael", "username": "developer@getport.io", "email": "developer@getport.io", "avatarUrl": "https://gravatar.com/avatar/9645cd28334383caa5efa6a681dddf7cba33f94ddaf234297ba13cb30d5c5718?s=32&d=mm", "isActive": true, "hasPasswordAuth": true, "isManaged": false, "dateJoined": "2022-01-18T22:38:13.946094Z", "lastLogin": "2024-11-10T23:25:31.826834Z", "has2fa": false, "lastActive": "2024-11-11T07:32:23.490455Z", "isSuperuser": false, "isStaff": false, "experiments": {}, "emails": [ { "id": "1861335", "email": "developer@getport.io", "is_verified": false } ], "avatar": { "avatarType": "letter_avatar", "avatarUuid": null, "avatarUrl": null } }, "orgRole": "owner", "pending": false, "expired": false, "flags": { "idp:provisioned": false, "idp:role-restricted": false, "sso:linked": true, "sso:invalid": false, "member-limit:restricted": false, "partnership:restricted": false }, "dateCreated": "2022-01-18T22:33:43.222734Z", "inviteStatus": "approved", "inviterName": "Port Admin", "role": "owner", "roleName": "Owner" } ``` Port entityJSON ``` { "identifier": "developer@getport.io", "title": "Michael", "blueprint": "sentryUser", "icon": "Sentry", "team": [], "properties": { "username": "developer@getport.io", "isActive": true, "dateJoined": "2022-01-18T22:38:13.946094Z", "lastLogin": "2024-11-10T23:25:31.826834Z", "orgRole": "owner", "inviteStatus": "approved" }, "relations": {}, "createdAt": "2024-11-06T08:49:17.700Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-11-06T08:59:11.446Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML Enable Team Members The `includeMembers` flag is used to decide enrich the teams response with details about the members of the team. To turn this feature off, set it to `false`. ``` - kind: team selector: query: 'true' includeMembers: true port: entity: mappings: identifier: .slug title: .name blueprint: '"sentryTeam"' properties: dateCreated: .dateCreated memberCount: .memberCount roles: .teamRole projects: .projects | map (.slug) relations: members: if .__members != null then .__members | map(.user.email) | map(select(. != null)) else [] end ``` Sample payloadJSON ``` { "id": "1275104", "slug": "platform-team", "name": "Developer Experience", "dateCreated": "2021-11-16T13:25:53.617157Z", "isMember": true, "teamRole": "contributor", "flags": { "idp:provisioned": false }, "access": [ "org:read", "alerts:read", "project:releases", "event:write", "event:read", "project:read", "team:read", "member:read" ], "hasAccess": true, "isPending": false, "memberCount": 43, "avatar": { "avatarType": "letter_avatar", "avatarUuid": null }, "externalTeams": [], "projects": [ { "id": "4504592557998080", "slug": "admin-service", "name": "admin-service", "platform": "node", "dateCreated": "2023-01-30T08:35:19.602158Z", "isBookmarked": false, "isMember": true, "features": [ "first-event-severity-new-escalation", "minidump", "similarity-indexing", "similarity-view", "span-metrics-extraction", "span-metrics-extraction-addons", "releases" ], "firstEvent": null, "firstTransactionEvent": false, "access": [ "org:read", "alerts:read", "project:releases", "event:write", "event:read", "project:read", "team:read", "member:read" ], "hasAccess": true, "hasCustomMetrics": false, "hasMinifiedStackTrace": false, "hasMonitors": false, "hasProfiles": false, "hasReplays": false, "hasFeedbacks": false, "hasNewFeedbacks": false, "hasSessions": false, "hasInsightsHttp": false, "hasInsightsDb": false, "hasInsightsAssets": false, "hasInsightsAppStart": false, "hasInsightsScreenLoad": false, "hasInsightsVitals": false, "hasInsightsCaches": false, "hasInsightsQueues": false, "hasInsightsLlmMonitoring": false, "isInternal": false, "isPublic": false, "avatar": { "avatarType": "letter_avatar", "avatarUuid": null }, "color": "#3f8abf", "status": "active" }, { "id": "4508444173533184", "slug": "oauth-service", "name": "oauth-service", "platform": "node-fastify", "dateCreated": "2024-12-10T13:51:48.350544Z", "isBookmarked": false, "isMember": true, "features": [ "first-event-severity-new-escalation", "minidump", "similarity-indexing", "similarity-view", "span-metrics-extraction", "span-metrics-extraction-addons", "releases" ], "firstEvent": null, "firstTransactionEvent": false, "access": [ "org:read", "alerts:read", "project:releases", "event:write", "event:read", "project:read", "team:read", "member:read" ], "hasAccess": true, "hasCustomMetrics": false, "hasMinifiedStackTrace": false, "hasMonitors": false, "hasProfiles": false, "hasReplays": false, "hasFeedbacks": false, "hasNewFeedbacks": false, "hasSessions": false, "hasInsightsHttp": false, "hasInsightsDb": false, "hasInsightsAssets": false, "hasInsightsAppStart": false, "hasInsightsScreenLoad": false, "hasInsightsVitals": false, "hasInsightsCaches": false, "hasInsightsQueues": false, "hasInsightsLlmMonitoring": false, "isInternal": false, "isPublic": false, "avatar": { "avatarType": "letter_avatar", "avatarUuid": null }, "color": "#60bf3f", "status": "active" } ], "__members": [ { "id": "11033546", "email": "danny@domain.io", "name": "danny@domain.io", "user": { "id": "1823521", "name": "danny@domain.io", "username": "6032da5ae6c84433bb139023b23e3774", "email": "danny@domain.io", "avatarUrl": "https://gravatar.com/avatar/6fd8727dde707fd7bbf59ddde0f2a803416b082a2ddf538f6edfb0f9535a6dec?s=32&d=mm", "isActive": true, "hasPasswordAuth": false, "isManaged": false, "dateJoined": "2022-03-21T09:44:08.054654Z", "lastLogin": "2024-12-09T07:42:25.535883Z", "has2fa": false, "lastActive": "2024-12-18T13:02:41.565988Z", "isSuperuser": false, "isStaff": false, "experiments": {}, "emails": [ { "id": "1965065", "email": "danny@domain.io", "is_verified": false } ], "avatar": { "avatarType": "letter_avatar", "avatarUuid": null, "avatarUrl": null } }, "orgRole": "member", "pending": false, "expired": false, "flags": { "idp:provisioned": false, "idp:role-restricted": false, "sso:linked": true, "sso:invalid": false, "member-limit:restricted": false, "partnership:restricted": false }, "dateCreated": "2022-03-21T09:44:09.037845Z", "inviteStatus": "approved", "inviterName": null, "role": "member", "roleName": "Member", "teamRole": null, "teamSlug": "getport" } ] } ``` Port entityJSON ``` { "identifier": "platform-team", "title": "Developer Experience", "blueprint": "sentryTeam", "icon": "Sentry", "properties": { "dateCreated": "2022-11-16T13:25:53.617157Z", "memberCount": 1, "roles": "contributor", "projects": [ "admin-service", "oauth-service" ] }, "relations": { "members": [ "danny@domain.io" ] }, "createdAt": "2023-11-06T08:49:17.700Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-11-06T08:59:11.446Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `includeMembers` | `boolean` | Whether to include the members of the team, defaults to false Default: `false`. | Default mappingYAML ``` - kind: project selector: query: "true" port: entity: mappings: identifier: .slug title: .name blueprint: '"sentryProject"' properties: dateCreated: .dateCreated platform: .platform status: .status link: .organization.links.organizationUrl + "/projects/" + .name ``` Sample payloadJSON ``` { "id": "4504931759095808", "slug": "python-fastapi", "name": "python-fastapi", "platform": "python-fastapi", "dateCreated": "2023-03-31T06:18:37.290732Z", "isBookmarked": false, "isMember": false, "features": [ "alert-filters", "minidump", "race-free-group-creation", "similarity-indexing", "similarity-view", "span-metrics-extraction", "span-metrics-extraction-resource", "releases" ], "firstEvent": "2023-03-31T06:25:54.666640Z", "firstTransactionEvent": false, "access": [], "hasAccess": true, "hasMinifiedStackTrace": false, "hasMonitors": false, "hasProfiles": false, "hasReplays": false, "hasFeedbacks": false, "hasSessions": false, "isInternal": false, "isPublic": false, "avatar": { "avatarType": "letter_avatar", "avatarUuid": null }, "color": "#913fbf", "status": "active", "organization": { "id": "4504931754901504", "slug": "test-org", "status": { "id": "active", "name": "active" }, "name": "Test Org", "dateCreated": "2023-03-31T06:17:33.619189Z", "isEarlyAdopter": false, "require2FA": false, "requireEmailVerification": false, "avatar": { "avatarType": "letter_avatar", "avatarUuid": null, "avatarUrl": null }, "features": [ "performance-tracing-without-performance", "performance-consecutive-http-detector", "performance-large-http-payload-detector", "escalating-issues", "minute-resolution-sessions", "performance-issues-render-blocking-assets-detector", "event-attachments" ], "links": { "organizationUrl": "https://test-org.sentry.io", "regionUrl": "https://us.sentry.io" }, "hasAuthProvider": false } } ``` Port entityJSON ``` { "identifier": "python-fastapi", "title": "python-fastapi", "icon": "Sentry", "blueprint": "sentryProject", "team": [], "properties": { "dateCreated": "2023-03-31T06:18:37.290732Z", "platform": "python-fastapi", "status": "active", "link": "https://test-org.sentry.io/projects/python-fastapi" }, "relations": {}, "createdAt": "2023-11-06T08:49:17.700Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-11-06T08:59:11.446Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------ | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `tag` | `string` | The name of the tag used to filter the resources. The default value is environment Default: `"environment"`. | Default mappingYAML ``` - kind: issue selector: query: "true" port: entity: mappings: identifier: ".id" title: ".title" blueprint: '"sentryIssue"' properties: link: ".permalink" status: ".status" isUnhandled: ".isUnhandled" relations: projectEnvironment: ".project.slug" assignedTo: .assignedTo.email ``` * Include Archived You can use the `includeArchived` selector to filter archived/ignored issues. By default, this selector is set to `true` ``` - kind: issue selector: query: "true" includeArchived: false ``` Sample payloadJSON ``` { "id": "4605173695", "shareId": "None", "shortId": "PYTHON-FASTAPI-2", "title": "ZeroDivisionError: division by zero", "culprit": "index", "permalink": "https://test-org.sentry.io/issues/4605173695/", "logger": "None", "level": "error", "status": "unresolved", "statusDetails": {}, "substatus": "new", "isPublic": false, "platform": "python", "project": { "id": "4504931759095808", "name": "python-fastapi", "slug": "python-fastapi", "platform": "python-fastapi" }, "type": "error", "metadata": { "value": "division by zero", "type": "ZeroDivisionError", "filename": "app.py", "function": "index", "display_title_with_tree_label": false, "in_app_frame_mix": "mixed" }, "numComments": 0, "assignedTo": { "email": "danny@domain.io", "id": "11033546", "name": "danny@domain.io" }, "isBookmarked": false, "isSubscribed": false, "subscriptionDetails": "None", "hasSeen": false, "annotations": [], "issueType": "error", "issueCategory": "error", "isUnhandled": true, "count": "1", "userCount": 0, "firstSeen": "2023-11-06T08:31:27.058163Z", "lastSeen": "2023-11-06T08:31:27.058163Z", "stats": { "24h": [ [1699174800, 0], [1699178400, 0], [1699182000, 0], [1699250400, 0], [1699254000, 0], [1699257600, 1] ] } } ``` Port entityJSON ``` { "identifier": "4605173695", "title": "ZeroDivisionError: division by zero", "icon": "Sentry", "blueprint": "sentryIssue", "team": [], "properties": { "link": "https://test-org.sentry.io/issues/4605173695/", "status": "unresolved", "isUnhandled": true }, "relations": { "project": "python-fastapi", "assignedTo": "danny@domain.io" }, "createdAt": "2023-11-06T08:49:20.406Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-11-06T08:49:20.406Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------ | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `tag` | `string` | The name of the tag used to filter the resources. The default value is environment Default: `"environment"`. | | `includeArchived` | `boolean` | Whether to include the archived issues, defaults to true Default: `true`. | Default mappingYAML Environment tags The `selector.tag` key in the `project-tag` kind defines which Sentry tag data is synced to Port. In the configuration provided below, you will ingest all `environment` tag from your Sentry account to Port. For instance, if a Sentry project has 3 environments namely development, staging and production, this configuration will create 3 entities in the `Sentry Project Environment` catalog. You will then use the `issue-tag` kind to connect each issue to its environment. ``` - kind: project-tag selector: query: 'true' tag: environment port: entity: mappings: identifier: .slug + "-" + .__tags.name title: .name + "-" + .__tags.name blueprint: '"sentryProjectEnvironment"' properties: dateCreated: .dateCreated platform: .platform status: .status link: .organization.links.organizationUrl + "/projects/" + .name - kind: issue-tag selector: query: 'true' tag: environment port: entity: mappings: identifier: .id + "-" + .item.name title: .title + " -" + " " + .item.name blueprint: '"sentryIssue"' properties: link: .permalink + "?environment=" + .item.name status: .status isUnhandled: .isUnhandled relations: projectEnvironment: (.project.slug as $slug | .item | "\($slug)-\(.name)") assignee: combinator: '"and"' rules: - operator: '"="' property: '"$identifier"' value: .assignedTo.email ``` Sample payloadJSON ``` { "id":"4504931759095808", "slug":"python-fastapi", "name":"python-fastapi", "platform":"python-fastapi", "dateCreated":"2023-03-31T06:18:37.290732Z", "isBookmarked":false, "isMember":false, "features":[ "alert-filters", "minidump", "race-free-group-creation", "similarity-indexing", "similarity-view", "span-metrics-extraction", "span-metrics-extraction-resource", "releases" ], "firstEvent":"2023-03-31T06:25:54.666640Z", "firstTransactionEvent":false, "access":[ ], "hasAccess":true, "hasMinifiedStackTrace":false, "hasMonitors":false, "hasProfiles":false, "hasReplays":false, "hasFeedbacks":false, "hasSessions":false, "isInternal":false, "isPublic":false, "avatar":{ "avatarType":"letter_avatar", "avatarUuid":null }, "color":"#913fbf", "status":"active", "organization":{ "id":"4504931754901504", "slug":"pages-org", "status":{ "id":"active", "name":"active" }, "name":"Pages Org", "dateCreated":"2023-03-31T06:17:33.619189Z", "isEarlyAdopter":false, "require2FA":false, "requireEmailVerification":false, "avatar":{ "avatarType":"letter_avatar", "avatarUuid":null, "avatarUrl":null }, "links":{ "organizationUrl":"https://pages-org.sentry.io", "regionUrl":"https://us.sentry.io" }, "hasAuthProvider":false }, "__tags":{ "key":"environment", "name":"production", "value":"production", "count":10, "lastSeen":"2024-03-04T17:17:33Z", "firstSeen":"2024-03-04T17:14:22Z" } } ``` Port entityJSON ``` { "identifier": "python-fastapi-production", "title": "python-fastapi-production", "icon": "Sentry", "blueprint": "sentryProjectEnvironment", "team": [], "properties": { "dateCreated": "2023-03-31T06:18:37.290732Z", "platform": "python-fastapi", "status": "active", "link": "https://test-org.sentry.io/projects/python-fastapi" }, "relations": { "team": [ "platform-team" ] }, "createdAt": "2024-03-07T12:18:17.111Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-03-07T12:31:52.041Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Sentry 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 Sentry 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Sentry is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Sentry can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) To update Port in real time when Sentry issues change, create an internal integration in Sentry and configure it to send issue webhooks to Port. ### Get the webhook URL[​](#get-the-webhook-url "Direct link to Get the webhook URL") * Hosted by Port * Self-hosted First create a custom webhook integration in Port, then connect it to Sentry in [Create the internal integration in Sentry](#create-the-internal-integration-in-sentry): 1. Go to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Select the **Webhook** tab. 3. Click **Custom integration**. For more details, see the [custom webhook integration](/context-lake/ingestion/ingest-data-into-port/webhook/ingest-via-webhooks-overview/.md) guide. 4. Enter a name for the integration (e.g Sentry Webhook). 5. Enter a description for the integration. 6. Click **Next**. 7. After the custom integration is created, open its card. 8. Select the **Mapping** tab. 9. In the **Configure Webhook event** section, copy the webhook URL. You'll paste it in the [Sentry internal integration](#create-the-internal-integration-in-sentry) steps below. 10. In the **Map the data from the external system into Port** section, add a mapping for Sentry issue events. The mapping uses [JQ](https://stedolan.github.io/jq/manual/#Basicfilters) to select and transform fields from the webhook payload. Test your mapping Use the **Test mapping** section to verify that the JQ mapping works with a Sentry webhook payload before saving. Configure `liveEvents.baseUrl` and webhook security in the **Self-hosted** tab above. Use the resulting webhook URL when you [create the internal integration in Sentry](#create-the-internal-integration-in-sentry) below. **Example mapping (click to expand)** ``` [ { "blueprint": "sentryIssue", "operation": "create", "filter": ".body.data.issue.id != null", "entity": { "identifier": ".body.data.issue.id", "title": ".body.data.issue.title", "properties": { "link": ".body.data.issue.permalink", "status": ".body.data.issue.status", "isUnhandled": ".body.data.issue.isUnhandled" }, "relations": { "projectEnvironment": ".body.data.issue.project.slug" } } } ] ``` **Example webhook payload (click to expand)** ``` { "body": { "action": "resolved", "data": { "issue": { "id": "7324623130", "shortId": "PROJECT-8V", "title": "N+1 Query", "permalink": "https://example.sentry.io/issues/7324623130/", "level": "info", "status": "resolved", "isUnhandled": false, "project": { "id": "4509038422523904", "name": "example-api", "slug": "example-api", "platform": "python-django" }, "firstSeen": "2026-03-10T14:44:04.743762Z", "lastSeen": "2026-06-09T11:20:54.358658Z" } }, "actor": { "type": "user", "name": "Example User" } }, "headers": { "Sentry-Hook-Resource": "issue", "Sentry-Hook-Signature": "", "Sentry-Hook-Timestamp": "1781025505" }, "queryParams": {} } ``` ### Create the internal integration in Sentry[​](#create-the-internal-integration-in-sentry "Direct link to Create the internal integration in Sentry") 1. Log in to Sentry with your organization's credentials. 2. Click the gear icon (Setting) at the left sidebar of the page. 3. Under **Integrations**, click **Custom Integrations**. 4. At the upper corner of this page, click on **Create New Integration**. 5. Sentry provides two types of integrations: Internal and Public. For the purpose of this guide, choose **Internal Integration** and click on the **Next** button. 6. Under **Internal Integration Details**, input the following details: * **Name** - use a meaningful name such as "Port Webhook". * **Webhook URL** - paste the URL from the [webhook URL instructions](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/sentry/.md?webhook-method=hosted-by-port#get-the-webhook-url) for your installation method. 7. Under **Permissions**, grant **Read** permissions for the **Issue & Event** category. 8. Under **Webhooks**, enable the **issues** checkbox to allow Sentry to report issue events to Port. 9. Click **Save Changes** at the bottom of the page. Save the client secret After saving the internal integration, Sentry displays a client secret. Copy it if you want Port to verify the signature of webhook requests from Sentry. For more details on setting up internal integrations in Sentry, see the [Sentry documentation](https://docs.sentry.io/organization/integrations/integration-platform/#internal-integrations). ### Verify webhook signatures[​](#verify-webhook-signatures "Direct link to Verify webhook signatures") Signature verification is optional. To enable it, use the client secret generated by Sentry when you created the internal integration. * Hosted by Port * Self-hosted 1. Select the custom webhook integration's **Settings** tab. 2. Paste the client secret from Sentry's internal integration in the **Secret** field. 3. Set **Signature Header Name** to `Sentry-Hook-Signature`. 4. Set **Signature Algorithm** to `sha256`. 5. Click **Save**. ![Webhook signature settings](/assets/images/webhook-settings-4430759e6a63dcd6648642d726349124.png) Configure `integration.secrets.sentryWebhookSecret` with the client secret — see the optional parameters in [Setup](#setup). ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # Argo CD ![ArgoCD icon](/img/guides/icons/ArgoCD.svg)![ArgoCD icon](/img/guides/icons/ArgoCD.svg) Loading version... Port's ArgoCD integration allows you to model ArgoCD resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[cluster](https://cd.apps.argoproj.io/swagger-ui#operation/ClusterService_List)[project](https://cd.apps.argoproj.io/swagger-ui#operation/ProjectService_List)[application](https://cd.apps.argoproj.io/swagger-ui#operation/ApplicationService_List)[deployment-history](https://cd.apps.argoproj.io/swagger-ui#operation/ApplicationService_List)[kubernetes-resource](https://cd.apps.argoproj.io/swagger-ui#operation/ApplicationService_List)[managed-resource](https://cd.apps.argoproj.io/swagger-ui#operation/ApplicationService_ManagedResources) ## Core support [Live eventsSee supported resources](#configure-real-time-updates) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### ArgoCD API token[​](#argocd-api-token "Direct link to ArgoCD API token") To generate a token: 1. Navigate to `/settings/accounts/`. For example, if you access your ArgoCD at `https://localhost:8080`, you should navigate to `https://localhost:8080/settings/accounts/` 2. The user should have `apiKey` capabilities to allow generating authentication tokens for API access. If you don't have a user created yet, follow the guide on [how to create a new ArgoCD user](https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/#create-new-user) 3. Newly created users may have limited scope to resources by default. For that reason, You will need to configure the RBAC policy for the new user by following [this guide](https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/) 4. Ensure that the policy definition grants enough permission to `read` resources such as `applications`, `clusters`, `projects`, `repositories` etc. 5. Under **Tokens** on your ArgoCD UI, Click **Generate New** to create a new token for the user or use the CLI: ``` argocd account generate-token --account ``` 6. Create an ArgoCD user with readonly permissions 1. Create an `argocd-user.yaml` file with the below manifest to create a new user `port-ocean-user` **Create user manifest (click to expand)** ``` apiVersion: v1 kind: ConfigMap metadata: name: argocd-cm namespace: argocd labels: app.kubernetes.io/name: argocd-cm app.kubernetes.io/part-of: argocd data: # add an additional local user with apiKey and login capabilities # apiKey - allows generating API keys # login - allows to login using UI accounts.port-ocean-user: apiKey, login accounts.port-ocean-user.enabled: "true" ``` 2. Apply the manifest with `kubectl` to create the user: ``` kubectl apply -f argocd-user.yaml ``` 3. Grant read only RBAC policy to the new user using the below manifest file (`argocd-rbac-cm.yaml`) **RBAC policy to grant readonly role to the new user (click to expand)** ``` apiVersion: v1 kind: ConfigMap metadata: name: argocd-rbac-cm namespace: argocd data: policy.default: role:readonly policy.csv: | p, role:read-only-role, applications, get, */*, allow p, role:read-only-role, clusters, get, *, allow p, role:read-only-role, projects, get, *, allow p, role:read-only-role, repositories, get, *, allow g, port-ocean-user, role:read-only-role ``` 4. Apply the `argocd-rbac-cm.yaml` manifest with `kubectl`: ``` kubectl apply -f argocd-rbac-cm.yaml ``` 5. Go to your ArgoCD UI to generate a new token for the user or use the CLI ``` argocd account generate-token --account ``` ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Self-hosted2Prerequisites3Install Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Prerequisites ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: cluster selector: query: 'true' port: entity: mappings: identifier: .name title: .name blueprint: '"argocdCluster"' properties: applicationsCount: .info.applicationsCount serverVersion: .serverVersion labels: .labels updatedAt: .connectionState.attemptedAt server: .server - kind: cluster selector: query: 'true' port: entity: mappings: identifier: .name + "-" + .item | tostring title: .name + "-" + .item blueprint: '"argocdNamespace"' relations: cluster: .name itemsToParse: .namespaces - kind: project selector: query: 'true' port: entity: mappings: identifier: .metadata.name title: .metadata.name blueprint: '"argocdProject"' properties: createdAt: .metadata.creationTimestamp description: .spec.description - kind: application selector: query: 'true' port: entity: mappings: identifier: .metadata.uid title: .metadata.name blueprint: '"argocdApplication"' properties: gitRepo: .spec.source.repoURL gitPath: .spec.source.path destinationServer: .spec.destination.server revision: .status.sync.revision targetRevision: .spec.source.targetRevision syncStatus: .status.sync.status healthStatus: .status.health.status createdAt: .metadata.creationTimestamp labels: .metadata.labels annotations: .metadata.annotations relations: project: .spec.project namespace: .metadata.namespace environment: combinator: '"and"' rules: - operator: '"="' property: '"argoCluster"' value: .spec.destination.server cluster: combinator: '"and"' rules: - operator: '"="' property: '"server"' value: .spec.destination.server - kind: application selector: query: 'true' port: entity: mappings: identifier: .metadata.uid + "-" + (.item.id | tostring) title: .metadata.name + "-" + (.item.id | tostring) blueprint: '"argocdDeploymentHistory"' properties: deployedAt: .item.deployedAt deployStartedAt: .item.deployStartedAt revision: .item.source.repoURL + "/commit/" + .item.revision initiatedBy: .item.initiatedBy.username repoURL: .item.source.repoURL sourcePath: .item.source.path relations: application: .metadata.uid anotherRelation: .item.source.repoURL itemsToParse: .status.history // [] ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to 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 [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found on the argocd [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd/example/.md). ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Configure real-time updates[​](#configure-real-time-updates "Direct link to Configure real-time updates") Currently, the ArgoCD REST API lacks support for programmatic webhook creation. To set up a webhook configuration in ArgoCD for sending notifications to the Ocean integration, follow these steps: 1. Install ArgoCD notifications manifest. ``` kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/release-1.0/manifests/install.yaml ``` 2. Install ArgoCD triggers and templates manifest. ``` kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-notifications/release-1.0/catalog/install.yaml ``` 3. Use `kubectl` to connect to the Kubernetes cluster where your ArgoCD instance is deployed. ``` kubectl config use-context ``` 4. Set the current namespace to your ArgoCD namespace, use the following command. ``` kubectl config set-context --current --namespace= ``` 5. Create a YAML file (e.g. `argocd-webhook-config.yaml`) that configures the webhook notification service.
Set up the following components to send real-time events whenever ArgoCD applications are updated: * Notification service definition. * Template for the webhook message body. * Trigger definitions. * Subscriptions to the notifications. Use the manifest below and replace `` with the ingress or service URL that exposes your Ocean integration. Keep `/integration/webhook` unchanged because Ocean uses this path for incoming webhook events. **Webhook manifest file (click to expand)** ``` apiVersion: v1 kind: ConfigMap metadata: name: argocd-notifications-cm data: trigger.on-sync-operation-change: | - description: Application syncing has updated send: - app-status-change when: app.status.operationState.phase in ['Error', 'Failed', 'Succeeded', 'Running'] trigger.on-deployed: | - description: Application is synced and healthy send: - app-status-change when: app.status.operationState.phase in ['Succeeded'] and app.status.health.status == 'Healthy' trigger.on-health-degraded: | - description: Application has degraded send: - app-status-change when: app.status.health.status == 'Degraded' service.webhook.port-ocean: | url: headers: - name: Content-Type value: application/json template.app-status-change: | webhook: port-ocean: method: POST path: /integration/webhook body: | { "action": "upsert", "application_name": "{{.app.metadata.name}}" } subscriptions: | - recipients: - port-ocean triggers: - on-deployed - on-health-degraded - on-sync-operation-change ``` 6. Use `kubectl` to apply the YAML file to your cluster. Run the following command, replacing `` with your ArgoCD namespace and `` with the actual path to your YAML file: ``` kubectl apply -n -f ``` This command deploys the webhook notification configuration to your ArgoCD notification configmap (`argocd-notifications-cm`), allowing Ocean to receive real-time events. ### Ocean streaming[​](#ocean-streaming "Direct link to Ocean streaming") The ArgoCD API does not support pagination. This can lead to high memory consumption in large environments when fetching all resources in a single request. To prevent this, you can enable streaming. When enabled, the integration saves the API response to disk and processes it in chunks, which significantly reduces memory usage. #### Enable streaming[​](#enable-streaming "Direct link to Enable streaming") Add the `streamingEnabled` parameter to the `helm upgrade` command: ``` --set streamingEnabled=true ``` #### Configure streaming[​](#configure-streaming "Direct link to Configure streaming") The streaming process uses a buffer to hold data chunks in memory. A larger buffer can speed up synchronization but also increases memory consumption. By default, the buffer size is 20MB and the chunk size is 64KB. You can customize these values using the following environment variables: ``` # Example: set chunk size to 64KB (65536 bytes) OCEAN__STREAMING__CHUNK_SIZE=65536 # Example: set max buffer size to 10MB (1024 * 1024 * 10) OCEAN__STREAMING__MAX_BUFFER_SIZE_MB=10485760 ``` ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found on the argocd [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd/example/.md) ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=ArgoCD). --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ## Cluster[​](#cluster "Direct link to Cluster") **Cluster blueprint (click to expand)** Create in Port ``` { "identifier": "argocdCluster", "description": "This blueprint represents an ArgoCD cluster", "title": "ArgoCD Cluster", "icon": "Argo", "schema": { "properties": { "applicationsCount": { "title": "Applications Count", "type": "number", "description": "The number of applications managed by Argo CD on the cluster", "icon": "DefaultProperty" }, "serverVersion": { "title": "Server Version", "type": "string", "description": "Contains information about the Kubernetes version of the cluster", "icon": "DefaultProperty" }, "labels": { "title": "Labels", "type": "object", "description": "Contains information about cluster metadata", "icon": "DefaultProperty" }, "updatedAt": { "icon": "DefaultProperty", "title": "Updated At", "type": "string", "format": "date-time" }, "server": { "title": "Server", "description": "The API server URL of the Kubernetes cluster", "type": "string", "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: cluster selector: query: "true" port: entity: mappings: identifier: .name title: .name blueprint: '"argocdCluster"' properties: applicationsCount: .info.applicationsCount serverVersion: .serverVersion labels: .labels updatedAt: .connectionState.attemptedAt server: .server ``` ## Namespace[​](#namespace "Direct link to Namespace") **Namespace blueprint (click to expand)** Create in Port ``` { "identifier": "argocdNamespace", "description": "This blueprint represents an ArgoCD namespace", "title": "ArgoCD Namespace", "icon": "Argo", "schema": { "properties": {}, "required": [] }, "aggregationProperties": {}, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "cluster": { "title": "ArgoCD Cluster", "target": "argocdCluster", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: cluster selector: query: "true" port: itemsToParse: .namespaces entity: mappings: identifier: .name + "-" + .item | tostring title: .name + "-" + .item blueprint: '"argocdNamespace"' properties: {} relations: cluster: .name ``` ## Project[​](#project "Direct link to Project") **Project blueprint (click to expand)** Create in Port ``` { "identifier": "argocdProject", "description": "This blueprint represents an ArgoCD Project", "title": "ArgoCD Project", "icon": "Argo", "schema": { "properties": { "createdAt": { "title": "Created At", "type": "string", "format": "date-time", "icon": "DefaultProperty" }, "description": { "title": "Description", "description": "Project description", "type": "string", "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: "true" port: entity: mappings: identifier: .metadata.name title: .metadata.name blueprint: '"argocdProject"' properties: createdAt: .metadata.creationTimestamp description: .spec.description ``` ## Application[​](#application "Direct link to Application") Breaking changes By default, ArgoCD retrieves applications using a server-side filter in the form of `selector: cluster=`. This means only applications with a matching cluster label are returned, and any applications without this label are excluded. Starting from version 0.3.0 of the integration, this behavior can be customized. You can enable server-side filtering using the optional queryParams field, which allows you to define your own filtering criteria. The supported format follows the structure described in the [ArgoCD API documentation](https://argo-cd.readthedocs.io/en/latest/developer-guide/api-docs/). **Application blueprint (click to expand)** Create in Port ``` { "identifier": "argocdApplication", "description": "This blueprint represents an ArgoCD Application", "title": "Running Service", "icon": "Argo", "schema": { "properties": { "gitRepo": { "type": "string", "icon": "Git", "title": "Repository URL", "description": "The URL of the Git repository containing the application source code" }, "gitPath": { "type": "string", "title": "Path", "description": "The path within the Git repository where the application manifests are located" }, "destinationServer": { "type": "string", "title": "Destination Server", "description": "The URL of the target cluster's Kubernetes control plane API" }, "revision": { "type": "string", "title": "Revision", "description": "Revision contains information about the revision the comparison has been performed to" }, "targetRevision": { "type": "string", "title": "Target Revision", "description": "Target Revision defines the revision of the source to sync the application to. In case of Git, this can be commit, tag, or branch" }, "syncStatus": { "type": "string", "title": "Sync Status", "enum": [ "Synced", "OutOfSync", "Unknown" ], "enumColors": { "Synced": "green", "OutOfSync": "red", "Unknown": "lightGray" }, "description": "Status is the sync state of the comparison" }, "healthStatus": { "type": "string", "title": "Health Status", "enum": [ "Healthy", "Missing", "Suspended", "Degraded", "Progressing", "Unknown" ], "enumColors": { "Healthy": "green", "Missing": "yellow", "Suspended": "purple", "Degraded": "red", "Progressing": "blue", "Unknown": "lightGray" }, "description": "Status holds the status code of the application or resource" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time", "description": "The created timestamp of the application" }, "labels": { "type": "object", "title": "Labels", "description": "Map of string keys and values that can be used to organize and categorize object" }, "annotations": { "type": "object", "title": "Annotations", "description": "Annotations are unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "project": { "title": "ArgoCD Project", "target": "argocdProject", "required": false, "many": false }, "cluster": { "title": "ArgoCD Cluster", "target": "argocdCluster", "required": false, "many": false }, "namespace": { "title": "ArgoCD Namespace", "target": "argocdNamespace", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: application selector: query: "true" port: entity: mappings: identifier: .metadata.uid title: .metadata.name blueprint: '"argocdApplication"' properties: gitRepo: .spec.source.repoURL gitPath: .spec.source.path destinationServer: .spec.destination.server revision: .status.sync.revision targetRevision: .spec.source.targetRevision syncStatus: .status.sync.status healthStatus: .status.health.status createdAt: .metadata.creationTimestamp labels: .metadata.labels annotations: .metadata.annotations relations: project: .spec.project namespace: .metadata.namespace cluster: .spec.destination.name ``` ### Include Query Parameters[​](#include-query-parameters "Direct link to Include Query Parameters") The optional `queryParams` field enables server-side filtering when retrieving ArgoCD applications. It supports the following ArgoCD application query parameters: * repo * appNamespace * selector * projects * resourceVersion This field is not applied by default and must be explicitly defined. For example, given an application with labels like: ``` { "metadata": { "name": "test-application-001", "namespace": "argocd", "labels": { "app-type": "analytics-svc", "cluster": "prod-ap-northeast-1-006", "env": "prod", "instance": "001" } } } ``` You can apply server-side filtering using the `selector` parameter within `queryParams`. The configuration below filters applications belonging to the `argocd` namespace and limits results to those with the label `cluster=prod-ap-northeast-1-006`: ``` - kind: application selector: query: "true" queryParams: selector: "cluster=prod-ap-northeast-1-006" appNamespace: "argocd" startTime: 1 ``` ## Deployment history[​](#deployment-history "Direct link to Deployment history") Deployment history requires sync operations Deployment history data in ArgoCD is populated only after sync operations occur on an application. If your application has never been synced, the `.status.history` field will be empty and no deployment history entities will appear in Port. You do not need to explicitly set `revisionHistoryLimit` in your ArgoCD Application manifest, ArgoCD applies a default value of 10 internally. Once sync operations have been performed, history entries will be recorded and ingested by the integration automatically. **Deployment history blueprint (click to expand)** Create in Port ``` { "identifier": "argocdDeploymentHistory", "description": "This blueprint represents an ArgoCD deployment history", "title": "ArgoCD Deployment History", "icon": "Argo", "schema": { "properties": { "deployedAt": { "title": "Deployed At", "type": "string", "format": "date-time" }, "deployStartedAt": { "title": "Deploy Started At", "type": "string", "format": "date-time" }, "revision": { "title": "Revision", "type": "string" }, "initiatedBy": { "title": "Initiated By", "type": "string" }, "repoURL": { "title": "Repository URL", "type": "string" }, "sourcePath": { "title": "Source Path", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "application": { "title": "Application", "target": "argocdApplication", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: application selector: query: "true" port: itemsToParse: .status.history entity: mappings: identifier: .metadata.uid + "-" + (.item.id | tostring) title: .metadata.name + "-" + (.item.id | tostring) blueprint: '"argocdDeploymentHistory"' properties: deployedAt: .item.deployedAt deployStartedAt: .item.deployStartedAt revision: .item.source.repoURL + "/commit/" + .item.revision initiatedBy: .item.initiatedBy.username repoURL: .item.source.repoURL sourcePath: .item.source.path relations: application: .metadata.uid ``` ## Kubernetes Resource[​](#kubernetes-resource "Direct link to Kubernetes Resource") **Images blueprint (click to expand)** Create in Port ``` { "identifier": "image", "description": "This blueprint represents an image", "title": "Image", "icon": "AWS", "schema": { "properties": {}, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Kubernetes resource blueprint (click to expand)** Create in Port ``` { "identifier": "argocdKubernetesResource", "description": "This blueprint represents an ArgoCD kubernetes resource", "title": "Kubernetes Resource", "icon": "Argo", "schema": { "properties": { "kind": { "title": "Kind", "type": "string" }, "version": { "title": "Version", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "labels": { "type": "object", "title": "Labels" }, "annotations": { "type": "object", "title": "Annotations" } }, "required": [] }, "mirrorProperties": { "healthStatus": { "title": "Health Status", "path": "application.healthStatus" }, "syncStatus": { "title": "Sync Status", "path": "application.syncStatus" } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "application": { "title": "Application", "target": "argocdApplication", "required": false, "many": false }, "image": { "title": "Image", "target": "image", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: managed-resource selector: query: "true" port: entity: mappings: identifier: .__application.metadata.uid + "-" + .kind + "-" + .name title: .__application.metadata.name + "-" + .kind + "-" + .name blueprint: '"argocdKubernetesResource"' properties: kind: .kind namespace: .namespace version: if .kind == "Deployment" then .resourceVersion else null end annotations: (.liveState // .targetState) | fromjson | .metadata.annotations labels: (.liveState // .targetState) | fromjson | .metadata.labels relations: application: .__application.metadata.uid image: >- if .kind == "Deployment" then (.liveState // .targetState) | fromjson | .spec.template.spec.containers[0].image else null end ``` ### Include App Filters[​](#include-app-filters "Direct link to Include App Filters") The optional `appFilters` field enables server-side filtering when retrieving ArgoCD applications. It supports the following parameters: * repo * appNamespace * selector * projects * resourceVersion This field is not applied by default and must be explicitly defined. For example, given an application with labels like: ``` { "metadata": { "name": "test-application-001", "namespace": "argocd", "labels": { "app-type": "analytics-svc", "cluster": "prod-ap-northeast-1-006", "env": "prod", "instance": "001" } } } ``` You can apply server-side filtering using the `selector` parameter within `queryParams`. The configuration below filters applications belonging to the `argocd` namespace and limits results to those with the label `cluster=prod-ap-northeast-1-006`: ``` - kind: managed-resource selector: query: "true" appFilters: selector: "cluster=prod-ap-northeast-1-006" appNamespace: "argocd" ``` --- # n8n Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's n8n integration ingests n8n resources into your software catalog using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. It focuses on three key data sources: users, projects, and workflows. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The n8n integration can ingest the following resources into Port: * `n8nUser` – n8n users from `/v1/users`. * `n8nProject` – n8n projects from `/v1/projects`. * `n8nWorkflow` – n8n workflows from `/v1/workflows`. These resources provide visibility into your n8n automation platform, including user management, project organization, and workflow definitions. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, you need: 1. Port credentials (`CLIENT_ID` and `CLIENT_SECRET`) 2. n8n instance URL (e.g., `https://your-instance.com/api`) 3. n8n API key (see [n8n API docs](https://docs.n8n.io/api/authentication/)) **To create an n8n API key:** 1. Navigate to your n8n instance and sign in to your account. 2. Go to **Settings** → **API**. 3. Click **Create API Key**. 4. Give your key a name (e.g., "Port Integration") and copy the API key. 5. Store the API key securely. API key security Store your API key securely and never share it. The key provides access to your n8n instance data. Review the [n8n API reference](https://docs.n8n.io/api/api-reference/) to understand the exact response structure returned by each endpoint. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. Configure: * **Identifier**: `n8n-integration`. * **Base URL**: your n8n instance API URL (for example `https://your-instance.com/api`). * **Authentication type**: `api_key`. * **API key header**: `X-N8N-API-KEY`. * **API key**: your n8n API key. 3. Expand **Advanced configuration** and set: * **Pagination type**: `cursor`. * **Pagination parameter**: `cursor`. * **Page size parameter**: `limit`. * **Page size**: `100`. * **Cursor path**: `nextCursor`. * **Has more path**: `nextCursor`. 4. Click **Save & install**. Continue with the mappings After the integration is installed, continue to the **Configuration** section on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, `YOUR_N8N_INSTANCE_URL`, and `YOUR_N8N_API_KEY`. ``` helm repo add port-labs https://port-labs.github.io/helm-charts helm repo update helm install n8n-integration port-labs/port-ocean \ --set initializePortResources=true \ --set scheduledResyncInterval=120 \ --set sendRawDataExamples=true \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.identifier=n8n-integration \ --set integration.config.baseUrl=https://your-instance.com/api \ --set integration.config.authType=api_key \ --set integration.config.apiKeyHeader=X-N8N-API-KEY \ --set integration.secrets.apiKey=YOUR_N8N_API_KEY \ --set integration.config.paginationType=cursor \ --set integration.config.paginationParam=cursor \ --set integration.config.sizeParam=limit \ --set integration.config.pageSize=100 \ --set integration.config.cursorPath=nextCursor \ --set integration.config.hasMorePath=nextCursor \ --set port.clientId=YOUR_PORT_CLIENT_ID \ --set port.clientSecret=YOUR_PORT_CLIENT_SECRET \ --set port.baseUrl="https://api.port.io" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters | Parameter | Description | Example | Required | | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | -------- | | `port.clientId` | Your Port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.baseUrl` | Your Port API URL (`https://api.port.io` for EU, `https://api.us.port.io` for US). | | ✅ | | `integration.config.baseUrl` | Base URL for your n8n instance API. | | ✅ | | `integration.config.authType` | Authentication type for n8n (use `api_key` for n8n). | api\_key | ✅ | | `integration.config.apiKeyHeader` | Header name for the API key. | X-N8N-API-KEY | ✅ | | `integration.secrets.apiKey` | n8n API key. | | ✅ | | `integration.config.paginationType` | Pagination type used by n8n API. | cursor | ✅ | | `integration.config.paginationParam` | Query parameter name for pagination cursor. | cursor | ✅ | | `integration.config.sizeParam` | Query parameter name for page size. | limit | ✅ | | `integration.config.pageSize` | Number of items per page. | 100 | ✅ | | `integration.config.cursorPath` | JSON path to cursor value in response. | nextCursor | ✅ | | `integration.config.hasMorePath` | JSON path to check if more pages exist. | nextCursor | ✅ | | `integration.eventListener.type` | The event listener type. Read more about [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) | POLLING | ✅ | | `integration.type` | The integration type (must be `custom` for the no-code Ocean custom integration).. | custom | ✅ | | `integration.identifier` | Unique identifier for this integration instance. | n8n-integration | ✅ | | `initializePortResources` | Create default blueprints and mappings on first run. | true | ❌ | | `sendRawDataExamples` | Send sample payloads for easier mapping. | true | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, `YOUR_N8N_INSTANCE_URL`, and `YOUR_N8N_API_KEY`. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__IDENTIFIER=n8n-integration \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://your-instance.com/api" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="api_key" \ -e OCEAN__INTEGRATION__CONFIG__API_KEY_HEADER="X-N8N-API-KEY" \ -e OCEAN__INTEGRATION__CONFIG__API_KEY="YOUR_N8N_API_KEY" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="cursor" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_PARAM="cursor" \ -e OCEAN__INTEGRATION__CONFIG__SIZE_PARAM="limit" \ -e OCEAN__INTEGRATION__CONFIG__PAGE_SIZE="100" \ -e OCEAN__INTEGRATION__CONFIG__CURSOR_PATH="nextCursor" \ -e OCEAN__INTEGRATION__CONFIG__HAS_MORE_PATH="nextCursor" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before syncing data, create the blueprints that define your n8n entities (users, projects, and workflows). **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click the `+ Blueprint` button. 3. Copy each blueprint JSON from the sections below. **n8n User blueprint (click to expand)** Create in Port ``` { "identifier": "n8nUser", "description": "This blueprint represents an n8n user", "title": "n8n User", "icon": "User", "schema": { "properties": { "created_at": { "type": "string", "title": "Created At", "format": "date-time" }, "is_active": { "type": "boolean", "title": "Is Active" }, "email": { "type": "string", "title": "Email" }, "full_name": { "type": "string", "title": "Full Name" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **n8n Project blueprint (click to expand)** Create in Port ``` { "identifier": "n8nProject", "description": "This blueprint represents an n8n project", "title": "n8n Project", "icon": "OpenFolder", "schema": { "properties": {}, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **n8n Workflow blueprint (click to expand)** Create in Port ``` { "identifier": "n8nWorkflow", "description": "This blueprint represents an n8n workflow", "title": "n8n Workflow", "icon": "Pipeline", "schema": { "properties": { "created_at": { "type": "string", "title": "Created At", "format": "date-time" }, "updated_at": { "type": "string", "title": "Updated At", "format": "date-time" }, "is_active": { "type": "boolean", "title": "Is Active" }, "is_archived": { "type": "boolean", "title": "Is Archived" }, "nodes": { "items": { "type": "object" }, "icon": "DefaultProperty", "type": "array", "title": "Nodes" }, "connections": { "type": "object", "title": "Connections" }, "trigger_count": { "type": "number", "title": "Trigger Count" }, "tags": { "items": { "type": "string" }, "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "n8nProject", "required": false, "many": false } } } ``` 4. Click `Save` after each blueprint is added. ## Configuration[​](#configuration "Direct link to Configuration") Each resource maps an n8n endpoint to the Port entities defined above. **Key mapping components:** * **`kind`** – API endpoint path appended to your n8n base URL. * **`selector`** – Request payload, pagination controls, and data selection logic. * **`port.entity.mappings`** – JQ expressions that transform the API payload into Port entities. To configure the mapping: 1. Go to the [connectors page](https://app.port.io/settings/data-sources) of your portal. 2. Find your n8n integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below. **Complete n8n mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: /v1/users selector: query: 'true' data_path: .data port: entity: mappings: identifier: .id title: .email blueprint: '"n8nUser"' properties: full_name: >- if (.firstName // null) != null or (.lastName // null) != null then ((.firstName // "") + " " + (.lastName // "")) else .email end is_active: (.isPending | not) created_at: .createdAt email: .email - kind: /v1/projects selector: query: 'true' data_path: .data port: entity: mappings: identifier: .id title: .name blueprint: '"n8nProject"' properties: {} - kind: /v1/workflows selector: query: 'true' data_path: .data port: entity: mappings: identifier: .id title: .name blueprint: '"n8nWorkflow"' properties: nodes: .nodes connections: .connections trigger_count: .triggerCount is_active: .active is_archived: .isArchived tags: (.tags | map(.name)) created_at: .createdAt updated_at: .updatedAt relations: project: .shared[0].projectId ``` 5. Click `Save` to save the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to expand beyond the starter resources, use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md) to: 1. Test additional n8n endpoints. 2. Explore the response shape and detected property types. 3. Generate blueprint JSON and mapping snippets automatically. 4. Export installation commands with your configuration pre-filled. Start with the users, projects, and workflows entities above, then add more resources (such as executions or credentials) once you verify the value. ## API Reference[​](#api-reference "Direct link to API Reference") * [n8n Projects API](https://docs.n8n.io/api/api-reference/#tag/projects/get/projects) * [n8n Users API](https://docs.n8n.io/api/api-reference/#tag/user/get/users) * [n8n Workflows API](https://docs.n8n.io/api/api-reference/#tag/workflow/get/workflows) --- # Zapier Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's Zapier integration lets you ingest Zapier apps and zaps into your software catalog using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. ## Supported resources[​](#supported-resources "Direct link to Supported resources") This integration ingests the following resources: * `zapierApp` from the `GET /v2/apps` endpoint. * `zap` from the `GET /v2/zaps` endpoint. It is possible to reference any field that appears in the API responses linked above in the mapping configuration. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, make sure you have: * A Zapier account with API access. * A Port client ID and client secret. * Permission to create blueprints and data-source mappings in Port. ## Authentication[​](#authentication "Direct link to Authentication") Zapier allows two authentication methods for this setup: 1. OAuth 2.0 Authorization Code flow (rolling refresh tokens). 2. Client id passed as a query parameter. ### User access token authentication[​](#user-access-token-authentication "Direct link to User access token authentication") Refresh token rotation Zapier refresh tokens rotate: every refresh request can return a new refresh token and immediately invalidate the old one. To keep sync working, you need an external workflow that refreshes tokens and updates the new refresh token in your Port secrets every time it changes. #### Automate token refresh with GitHub Actions[​](#automate-token-refresh-with-github-actions "Direct link to Automate token refresh with GitHub Actions") You can use this scheduled GitHub Actions workflow to refresh your Zapier token and update repository secrets. 1. Add these repository secrets: `ZAPIER_CLIENT_ID`, `ZAPIER_CLIENT_SECRET`, `ZAPIER_OAUTH_REFRESH_TOKEN`, and `GH_ADMIN_TOKEN`. 2. Set `GH_ADMIN_TOKEN` to a GitHub personal access token with permission to update repository secrets. 3. Reference `ZAPIER_OAUTH_ACCESS_TOKEN` in your integration runtime. **GitHub Actions workflow example (click to expand)** ``` name: Refresh Zapier OAuth token on: schedule: - cron: "0 */6 * * *" workflow_dispatch: jobs: refresh-token: runs-on: ubuntu-latest steps: - name: Refresh Zapier access token id: refresh env: CLIENT_ID: ${{ secrets.ZAPIER_CLIENT_ID }} CLIENT_SECRET: ${{ secrets.ZAPIER_CLIENT_SECRET }} REFRESH_TOKEN: ${{ secrets.ZAPIER_OAUTH_REFRESH_TOKEN }} run: | response=$(curl -sS -X POST "https://api.zapier.com/oauth/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=refresh_token" \ --data-urlencode "client_id=$CLIENT_ID" \ --data-urlencode "client_secret=$CLIENT_SECRET" \ --data-urlencode "refresh_token=$REFRESH_TOKEN") access_token=$(echo "$response" | jq -r '.access_token') refresh_token=$(echo "$response" | jq -r '.refresh_token') if [ -z "$access_token" ] || [ "$access_token" = "null" ]; then echo "Failed to refresh token: $response" exit 1 fi echo "access_token=$access_token" >> "$GITHUB_OUTPUT" echo "refresh_token=$refresh_token" >> "$GITHUB_OUTPUT" - name: Update GitHub secrets env: GH_TOKEN: ${{ secrets.GH_ADMIN_TOKEN }} ACCESS_TOKEN: ${{ steps.refresh.outputs.access_token }} REFRESH_TOKEN: ${{ steps.refresh.outputs.refresh_token }} run: | gh secret set ZAPIER_OAUTH_ACCESS_TOKEN --body "$ACCESS_TOKEN" gh secret set ZAPIER_OAUTH_REFRESH_TOKEN --body "$REFRESH_TOKEN" ``` ### Client ID Authentication[​](#client-id-authentication "Direct link to Client ID Authentication") The `GET /v2/apps` endpoint requires a `client_id` query parameter and does not require OAuth. In No-code Ocean custom integration mappings, query params are typically not backed by Port secrets. Treat `client_id` as configuration and avoid placing sensitive values directly in mapping fields. ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before data can sync, create the required blueprints in Port: 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Create blueprints for `zapierApp` and `zap`. 3. Copy and paste each blueprint JSON from the sections below. 4. Click **Save**. **Zapier app blueprint (click to expand)** Create in Port ``` { "identifier": "zapierApp", "title": "Zapier App", "icon": "Zapier", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "image": { "title": "Image", "type": "string", "format": "url" }, "hex_color": { "title": "Hex Color", "type": "string" }, "action_types": { "title": "Action Types", "type": "array" }, "categories": { "title": "Categories", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Zap blueprint (click to expand)** Create in Port ``` { "identifier": "zap", "title": "Zap", "icon": "Zapier", "schema": { "properties": { "state": { "title": "State", "type": "string" }, "url": { "title": "Zap URL", "type": "string", "format": "url" }, "modified_at": { "title": "Modified At", "type": "string", "format": "date-time" }, "trigger_app": { "title": "Trigger App", "type": "string" }, "action_apps": { "title": "Action Apps", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom). 2. Click **+ Connector** and choose **No-code Ocean custom integration**. 3. In the configuration form, enter the following values: * **Identifier**: `my-ocean-zapier-integration`. * **Base URL**: `https://api.zapier.com`. * **Authentication type**: `bearer_token`. * **Token**: your Zapier OAuth access token. * **Pagination type**: `link`. * **Next link path**: `links.next`. 4. Click **Save & install**. Continue with the mappings After the integration is installed, continue to the **Resource mappings** section on this page. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation 1. Add Port's Helm repo and install the no-code Ocean custom integration. Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_ZAPIER_OAUTH_ACCESS_TOKEN`. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-zapier-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set createPortResourcesOrigin="Empty" \ --set scheduledResyncInterval=60 \ --set integration.identifier="my-ocean-zapier-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://api.zapier.com" \ --set integration.config.authType="bearer_token" \ --set integration.config.apiToken="YOUR_ZAPIER_OAUTH_ACCESS_TOKEN" \ --set integration.config.paginationType="link" \ --set integration.config.nextLinkPath="links.next" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters | Parameter | Description | Example | Required | | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -------- | | `port.clientId` | Your Port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.baseUrl` | Your Port API URL - `https://api.getport.io` for EU, `https://api.us.getport.io` for US. | | ✅ | | `integration.config.baseUrl` | Zapier API base URL. | | ✅ | | `integration.config.authType` | The authentication type for the API. Use `bearer_token` when you authenticate with an OAuth access token. | bearer\_token | ✅ | | `integration.config.apiToken` | Zapier OAuth access token to use in `Authorization: Bearer `. | | ✅ | | `integration.config.paginationType` | Pagination strategy. Use `link` to follow the next URL in Zapier responses. | link | ✅ | | `integration.config.nextLinkPath` | Path to the next page URL in the response body for link pagination. | links.next | ✅ | | `integration.eventListener.type` | The event listener type. Read more about [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md). | POLLING | ✅ | | `integration.type` | The integration type (must be `custom` for the no-code Ocean custom integration). | custom | ✅ | | `integration.identifier` | Unique identifier for the integration instance. | my-ocean-zapier-integration | ✅ | | `scheduledResyncInterval` | The number of minutes between each resync. When not set the integration will resync for each event listener resync event. Read more about [scheduledResyncInterval](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync). | 60 | ❌ | | `createPortResourcesOrigin` | Set to `Empty` to register only the integration without creating default blueprints or mappings. This guide creates blueprints and mappings manually. | Empty | ❌ | | `sendRawDataExamples` | Enable sending raw data examples from the third party API to Port for testing and managing the integration mapping. Default is true. | true | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_ZAPIER_OAUTH_ACCESS_TOKEN`. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__CREATE_PORT_RESOURCES_ORIGIN="Empty" \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://api.zapier.com" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="YOUR_ZAPIER_OAUTH_ACCESS_TOKEN" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="link" \ -e OCEAN__INTEGRATION__CONFIG__NEXT_LINK_PATH="links.next" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Resource mappings[​](#resource-mappings "Direct link to Resource mappings") Use the mappings below to ingest Zapier apps and zaps. ### Zapier apps (`GET /v2/apps`)[​](#zapier-apps-get-v2apps "Direct link to zapier-apps-get-v2apps") ``` resources: - kind: /v2/apps selector: query: 'true' data_path: '.data' query_params: client_id: "" port: entity: mappings: identifier: .id title: .title blueprint: '"zapierApp"' properties: description: .description image: .image hex_color: .hex_color action_types: .action_types categories: '[.categories[].slug]' ``` ### Zaps (`GET /v2/zaps`)[​](#zaps-get-v2zaps "Direct link to zaps-get-v2zaps") ``` resources: - kind: /v2/zaps selector: query: 'true' data_path: '.data' port: entity: mappings: identifier: .id title: .title blueprint: '"zap"' properties: state: .is_enabled url: .links.html_editor modified_at: .updated_at trigger_app: '[.steps[] | select(.type_of == "read")] | first | .app.title' action_apps: '[.steps[] | select(.type_of == "write") | .app.title]' ``` ## Configuration[​](#configuration "Direct link to Configuration") After installation, configure mappings in Port so Ocean can transform Zapier API responses into Port entities. The mapping `resources[].kind` must match the endpoint path you want to call, relative to your `OCEAN__INTEGRATION__CONFIG__BASE_URL`. You can use [JQ expressions](https://jqlang.org/manual/) in mappings to filter, reshape, and enrich Zapier data before sync. ## Next step[​](#next-step "Direct link to Next step") Once you pick your exact Zapier endpoints and blueprint schema, update the `resources[]` mappings to match the real request and response payloads from your Zapier account. ## References[​](#references "Direct link to References") * [Zapier API overview](https://docs.zapier.com/powered-by-zapier/api-reference/authentication). * [Get apps \[v2\] (`GET /v2/apps`)](https://docs.zapier.com/powered-by-zapier/api-reference/apps/get-apps-%5Bv2%5D). * [Get zaps \[v2\] (`GET /v2/zaps`)](https://docs.zapier.com/powered-by-zapier/api-reference/zaps/get-zaps-%5Bv2%5D). * [OAuth v2 guide](https://docs.zapier.com/powered-by-zapier/api-reference/authentication). --- # Jenkins ![Jenkins icon](/img/guides/icons/Jenkins.svg)![Jenkins icon](/img/guides/icons/Jenkins.svg) Loading version... Port's Jenkins integration allows you to model Jenkins resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Job](https://www.jenkins.io/doc/book/using/remote-access-api/)[Build](https://www.jenkins.io/doc/book/using/remote-access-api/)[User](https://www.jenkins.io/doc/book/using/remote-access-api/)[Stage](https://plugins.jenkins.io/pipeline-rest-api/) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * **Jenkins API token** — In Jenkins, click your username and navigate to **Configure** > **API Token** to generate a token to use as the `JENKINS_TOKEN`. * **Required plugins** — Install the following via **Manage Jenkins** > **Plugins**: * [**People View**](https://plugins.jenkins.io/people-view/) — Required for user information API (Jenkins 2.452 and above). * [**Pipeline: Stage View**](https://plugins.jenkins.io/pipeline-stage-view/) — Required for fetching pipeline stage data. ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Create jenkins blueprints[​](#create-jenkins-blueprints "Direct link to Create jenkins blueprints") Before configuring the integration, create the following blueprints in your Port organization.
Follow the instructions below to create the blueprints: 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on `+ Blueprint`. 3. Click on the `{...} Edit JSON` button. 4. Copy and paste the following JSON configuration: Recommended order: job, then build, then user. **Jenkins Job blueprint (click to expand)** Create in Port ``` { "identifier": "jenkinsJob", "description": "This blueprint represents a job event from Jenkins", "title": "Jenkins Job", "icon": "Jenkins", "schema": { "properties": { "jobName": { "type": "string", "title": "Job Name" }, "jobStatus": { "type": "string", "title": "Job Status", "enum": ["created", "unknown", "passing", "failing"], "enumColors": { "passing": "green", "created": "darkGray", "failing": "red", "unknown": "orange" } }, "timestamp": { "type": "string", "format": "date-time", "title": "Timestamp", "description": "Last updated timestamp of the job" }, "url": { "type": "string", "title": "Project URL" }, "parentJob": { "type": "object", "title": "Parent Job" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Jenkins Build blueprint (click to expand)** Create in Port ``` { "identifier": "jenkinsBuild", "description": "This blueprint represents a build event from Jenkins", "title": "Jenkins Build", "icon": "Jenkins", "schema": { "properties": { "buildStatus": { "type": "string", "title": "Build Status", "enum": ["SUCCESS", "FAILURE", "UNSTABLE", "ABORTED"], "enumColors": { "SUCCESS": "green", "FAILURE": "red", "UNSTABLE": "yellow", "ABORTED": "darkGray" } }, "buildUrl": { "type": "string", "title": "Build URL", "description": "URL to the build" }, "timestamp": { "type": "string", "format": "date-time", "title": "Timestamp", "description": "Last updated timestamp of the build" }, "buildDuration": { "type": "number", "title": "Build Duration", "description": "Duration of the build" } }, "required": [] }, "mirrorProperties": { "previousBuildStatus": { "title": "Previous Build Status", "path": "previousBuild.buildStatus" } }, "calculationProperties": {}, "relations": { "parentJob": { "title": "Jenkins Job", "target": "jenkinsJob", "required": false, "many": false }, "previousBuild": { "title": "Previous Build", "target": "jenkinsBuild", "required": false, "many": false } } } ``` **Jenkins User blueprint (click to expand)** Create in Port ``` { "identifier": "jenkinsUser", "description": "This blueprint represents a jenkins user", "title": "Jenkins User", "icon": "Jenkins", "schema": { "properties": { "url": { "type": "string", "title": "URL", "format": "url" }, "lastUpdateTime": { "type": "string", "format": "date-time", "title": "Last Update", "description": "Last updated timestamp of the user" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 5. Click `Create` after copying each blueprint json to save it and repeat the process for the next blueprint. Stage blueprint The integration also supports mapping Jenkins pipeline stages, but there is no default `jenkinsStage` blueprint. If you want to ingest stage data, create a custom blueprint that matches the properties in the Stage mapping configuration below. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: job selector: query: 'true' port: entity: mappings: identifier: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") | .[:-1] title: .fullName blueprint: '"jenkinsJob"' properties: jobName: .name url: .url jobStatus: '{"notbuilt": "created", "blue": "passing", "red": "failing"}[.color]' timestamp: .time parentJob: .__parentJob - kind: build selector: query: 'true' maxBuildsPerJob: 100 port: entity: mappings: identifier: >- .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") | .[:-1] title: .displayName blueprint: '"jenkinsBuild"' properties: buildStatus: .result buildUrl: .url buildDuration: .duration timestamp: .timestamp / 1000 | todate relations: parentJob: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") | .[:-1] | gsub("-[0-9]+$"; "") previousBuild: >- if .previousBuild then (.previousBuild.url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") | .[:-1]) else null end - kind: user selector: query: 'true' port: entity: mappings: identifier: .user.id title: .user.fullName blueprint: '"jenkinsUser"' properties: url: .user.absoluteUrl lastUpdateTime: if .lastChange then (.lastChange/1000) else now end | strftime("%Y-%m-%dT%H:%M:%SZ") ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. JobBuildUserStage Default mappingYAML ``` - kind: job selector: query: 'true' port: entity: mappings: identifier: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") | .[:-1] title: .fullName blueprint: '"jenkinsJob"' properties: jobName: .name url: .url jobStatus: '{"notbuilt": "created", "blue": "passing", "red": "failing"}[.color]' timestamp: .time parentJob: .__parentJob ``` Sample payloadJSON ``` { "_class" : "hudson.model.FreeStyleProject", "displayName" : "Hello Job", "fullName" : "Hello Job", "name" : "Hello Job", "url" : "http://localhost:8080/job/Hello%20Job/", "buildable" : true, "builds" : [ { "_class" : "hudson.model.FreeStyleBuild", "displayName" : "#2", "duration" : 221, "fullDisplayName" : "Hello Job #2", "id" : "2", "number" : 2, "result" : "SUCCESS", "timestamp" : 1700569094576, "url" : "http://localhost:8080/job/Hello%20Job/2/" }, { "_class" : "hudson.model.FreeStyleBuild", "displayName" : "#1", "duration" : 2214, "fullDisplayName" : "Hello Job #1", "id" : "1", "number" : 1, "result" : "SUCCESS", "timestamp" : 1700567994163, "url" : "http://localhost:8080/job/Hello%20Job/1/" } ], "color" : "blue" } ``` Port entityJSON ``` { "identifier": "hello-job", "title": "Hello Job", "icon": "Jenkins", "blueprint": "jenkinsJob", "properties": { "jobName": "Hello Job", "url": "http://localhost:8080/job/Hello%20Job/", "jobStatus": "passing", "timestamp": "2023-09-08T14:58:14Z" }, "relations": {}, "createdAt": "2023-12-18T08:37:21.637Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-12-18T08:37:21.637Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML Build Limit The `maxBuildsPerJob` selector is an optional parameter that limits the number of builds fetched per job. By default it is set to `100`, meaning the last 100 builds of each job will be fetched. You can increase or decrease this value to control how much build history is ingested. ``` - kind: build selector: query: 'true' maxBuildsPerJob: 100 port: entity: mappings: identifier: >- .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") | .[:-1] title: .displayName blueprint: '"jenkinsBuild"' properties: buildStatus: .result buildUrl: .url buildDuration: .duration timestamp: .timestamp / 1000 | todate relations: parentJob: .url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") | .[:-1] | gsub("-[0-9]+$"; "") previousBuild: >- if .previousBuild then (.previousBuild.url | split("://")[1] | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") | .[:-1]) else null end ``` Sample payloadJSON ``` { "_class" : "hudson.model.FreeStyleBuild", "displayName" : "#2", "duration" : 221, "fullDisplayName" : "Hello Job #2", "id" : "2", "number" : 2, "result" : "SUCCESS", "timestamp" : 1700569094576, "url" : "http://localhost:8080/job/Hello%20Job/2/" } ``` Port entityJSON ``` { "identifier": "hello-job-2", "title": "Hello Job #2", "icon": "Jenkins", "blueprint": "jenkinsBuild", "properties": { "buildStatus": "SUCCESS", "buildUrl": "http://localhost:8080/job/Hello%20Job/2/", "buildDuration": 221, "timestamp": "2023-09-08T14:58:14Z" }, "relations": { "parentJob": "hello-job" }, "createdAt": "2023-12-18T08:37:21.637Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-12-18T08:37:21.637Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `maxBuildsPerJob` | `integer` | Number of builds to fetch. Defaults to 100 Default: `100`. | Default mappingYAML ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: .user.id title: .user.fullName blueprint: '"jenkinsUser"' properties: url: .user.absoluteUrl lastUpdateTime: if .lastChange then (.lastChange/1000) else now end | strftime("%Y-%m-%dT%H:%M:%SZ") ``` Sample payloadJSON ``` { "user" : { "absoluteUrl" : "http://localhost:8080/user/admin", "fullName" : "admin", "description" : "System Administrator", "id" : "admin" }, "lastChange" : 1700569094576 } ``` Port entityJSON ``` { "identifier": "admin", "title": "admin", "icon": "Jenkins", "blueprint": "jenkinsUser", "properties": { "url": "http://localhost:8080/user/admin", "lastUpdateTime": "2023-09-08T14:58:14Z" }, "relations": {}, "createdAt": "2023-12-18T08:37:21.637Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-12-18T08:37:21.637Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML Control Stage Fetching To prevent overwhelming your Ocean instance with potentially thousands of stages from Jenkins, the integration requires you to specify a specific job. This ensures that Ocean only retrieves stages related to that job, keeping things focused and efficient. **Important**: The integration will also fetch stages from all nested jobs within the specified job. ``` - kind: stage selector: query: 'true' # Example jobUrl - replace with your own Jenkins job URL jobUrl: http://your-jenkins-server/job/your-project/job/your-job port: entity: mappings: identifier: >- ._links.self.href | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") title: .name blueprint: '"jenkinsStage"' properties: status: .status startTimeMillis: .startTimeMillis durationMillis: .durationMillis stageUrl: env.OCEAN__INTEGRATION__CONFIG__JENKINS_HOST + ._links.self.href relations: parentBuild: >- ._links.self.href | sub("/execution/node/[0-9]+/wfapi/describe$"; "") | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") # Additional stage configurations follow the same pattern. - kind: stage selector: query: 'true' # Example jobUrl - replace with your own Jenkins job URL jobUrl: http://your-jenkins-server/job/your-project/job/another-job port: entity: mappings: identifier: >- ._links.self.href | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") title: .name blueprint: '"jenkinsStage"' properties: status: .status startTimeMillis: .startTimeMillis durationMillis: .durationMillis stageUrl: env.OCEAN__INTEGRATION__CONFIG__JENKINS_HOST + ._links.self.href relations: parentBuild: >- ._links.self.href | sub("/execution/node/[0-9]+/wfapi/describe$"; "") | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") - kind: stage selector: query: 'true' # Example jobUrl - replace with your own Jenkins job URL jobUrl: http://your-jenkins-server/job/your-project/job/third-job port: entity: mappings: identifier: >- ._links.self.href | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") title: .name blueprint: '"jenkinsStage"' properties: status: .status startTimeMillis: .startTimeMillis durationMillis: .durationMillis stageUrl: env.OCEAN__INTEGRATION__CONFIG__JENKINS_HOST + ._links.self.href relations: parentBuild: >- ._links.self.href | sub("/execution/node/[0-9]+/wfapi/describe$"; "") | sub("^.*?/"; "") | gsub("%20"; "-") | gsub("%252F"; "-") | gsub("/"; "-") ``` Sample payloadJSON ``` { "_links": { "self": { "href": "/job/Phalbert/job/salesdash/job/master/227/execution/node/17/wfapi/describe" } }, "id": "17", "name": "Declarative: Post Actions", "execNode": "", "status": "SUCCESS", "startTimeMillis": 1717073271079, "durationMillis": 51, "pauseDurationMillis": 0 } ``` Port entityJSON ``` { "identifier": "job-Phalbert-job-salesdash-job-master-229-execution-node-17-wfapi-describe", "title": "Declarative: Post Actions", "icon": "Jenkins", "blueprint": "jenkinsStage", "team": [], "properties": { "status": "SUCCESS", "startTimeMillis": 1717073272012, "durationMillis": 26, "stageUrl": "http://localhost:8080/job/Phalbert/job/salesdash/job/master/229/execution/node/17/wfapi/describe" }, "relations": { "parentBuild": "job-Phalbert-job-salesdash-job-master-229" }, "createdAt": "2024-08-28T10:27:33.549Z", "createdBy": "", "updatedAt": "2024-08-28T10:27:30.274Z", "updatedBy": "" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `jobUrl` | `string` | The URL of the Jenkins job to fetch stages for. | --- # Octopus Deploy Integration ![OctopusDeploy icon](/img/guides/icons/OctopusDeploy.svg)![OctopusDeploy icon](/img/guides/icons/OctopusDeploy.svg) Loading version... Port's Octopus Deploy integration allows you to model Octopus Deploy resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Space](https://octopus.com/docs/octopus-rest-api/examples/spaces)[Project](https://octopus.com/docs/octopus-rest-api/examples/projects)[Release](https://octopus.com/docs/octopus-rest-api/examples/releases)[Deployment](https://octopus.com/docs/octopus-rest-api/examples/deployments)[Machine](https://octopus.com/docs/infrastructure/deployment-targets) ## Core support [Live eventsSee supported resources](#set-up-live-events) Ingesting Additional Resources The integration supports additional resources, see the [ingest additional resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/mapping-extra-resources/.md) page for more information. ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: space selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusSpace"' properties: url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .Id description: .Description - kind: project selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusProject"' properties: url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .SpaceId + "/projects/" + .Id description: .Description isDisabled: .IsDisabled tenantedDeploymentMode: .TenantedDeploymentMode relations: space: .SpaceId - kind: release selector: query: 'true' port: entity: mappings: identifier: .Id title: .ProjectId + "(" + .Version + ")" blueprint: '"octopusRelease"' properties: version: .Version assembledDate: .Assembled channelId: .ChannelId releaseNotes: .ReleaseNotes url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .SpaceId + "/releases/" + .Id relations: project: .ProjectId - kind: deployment selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusDeployment"' properties: createdAt: .Created deployedBy: .DeployedBy taskId: .TaskId failureEncountered: .FailureEncountered comments: .Comments url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .SpaceId + "/deployments/" + .Id relations: release: .ReleaseId project: .ProjectId - kind: machine selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusMachine"' properties: roles: .Roles status: .HealthStatus url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .SpaceId + "/infrastructure/machines/" + .Id + "/settings" isDisabled: .IsDisabled operatingSystem: .OperatingSystem architecture: .Architecture statusSummary: .StatusSummary endpointType: .Endpoint.DeploymentTargetTypeId communicationStyle: .Endpoint.CommunicationStyle relations: space: .SpaceId ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [ingest additional resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/mapping-extra-resources/.md) page. SpaceProjectReleaseDeploymentMachine Default mappingYAML ``` - kind: space selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusSpace"' properties: url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .Id description: .Description ``` Sample payloadJSON ``` { "Id": "Spaces-1", "Name": "Default", "Slug": "default", "Description": "Description cannot be empty", "IsDefault": true, "IsPrivate": false, "TaskQueueStopped": false, "SpaceManagersTeams": [ "teams-administrators", "teams-managers", "teams-spacemanagers-Spaces-1" ], "SpaceManagersTeamMembers": [], "Icon": null, "ExtensionSettings": [], "LastModifiedOn": "0001-01-01T00:00:00.000+00:00", "Links": { "Self": "/api/spaces/Spaces-1", "SpaceHome": "/api/Spaces-1", "Web": "/app#/spaces/Spaces-1", "Logo": "/api/spaces/Spaces-1/logo?cb=2024.3.10989", "Search": "/api/spaces/Spaces-1/search" } } ``` Port entityJSON ``` { "identifier": "Spaces-1", "title": "Default", "icon": "Octopus", "blueprint": "octopusSpace", "team": [], "properties": { "url": "https://testport.octopus.app/app#/Spaces-1", "description": null }, "relations": {}, "createdAt": "2024-08-22T14:27:34.746Z", "createdBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT", "updatedAt": "2024-08-22T14:27:34.746Z", "updatedBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT" } ``` Default mappingYAML ``` - kind: project selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusProject"' properties: url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .SpaceId + "/projects/" + .Id description: .Description isDisabled: .IsDisabled tenantedDeploymentMode: .TenantedDeploymentMode relations: space: .SpaceId ``` Sample payloadJSON ``` { "Id": "Projects-1", "SpaceId": "Spaces-1", "VariableSetId": "variableset-Projects-1", "DeploymentProcessId": "deploymentprocess-Projects-1", "ClonedFromProjectId": null, "DiscreteChannelRelease": false, "IncludedLibraryVariableSetIds": [], "DefaultToSkipIfAlreadyInstalled": false, "TenantedDeploymentMode": "Untenanted", "DefaultGuidedFailureMode": "EnvironmentDefault", "VersioningStrategy": { "Template": null, "DonorPackage": null }, "ReleaseCreationStrategy": { "ChannelId": null, "ReleaseCreationPackage": null, "ReleaseCreationPackageStepId": null }, "Templates": [], "AutoDeployReleaseOverrides": [], "ReleaseNotesTemplate": null, "DeploymentChangesTemplate": null, "ForcePackageDownload": false, "Icon": { "Id": "map-signs", "Color": "#5E2EA2" }, "AllowIgnoreChannelRules": true, "ExecuteDeploymentsOnResilientPipeline": null, "ExtensionSettings": [], "Name": "Sample Project", "Slug": "sample-project", "Description": "Explore how to automate your deployments with Octopus Deploy.\n\nEdit the custom script step, tweak the variables, and run your first deployment. Experiment and make it your own!", "IsDisabled": false, "ProjectGroupId": "ProjectGroups-2", "LifecycleId": "Lifecycles-2", "AutoCreateRelease": false, "IsVersionControlled": false, "PersistenceSettings": { "Type": "Database" }, "ProjectConnectivityPolicy": { "SkipMachineBehavior": "None", "TargetRoles": [], "AllowDeploymentsToNoTargets": false, "ExcludeUnhealthyTargets": false }, "Links": { "Self": "/api/Spaces-1/projects/Projects-1", "Variables": "/api/Spaces-1/projects/Projects-1/variables", "Releases": "/api/Spaces-1/projects/Projects-1/releases{/version}{?skip,take,searchByVersion}", "Channels": "/api/Spaces-1/projects/Projects-1/channels{/id}{?skip,take,partialName}", "Triggers": "/api/Spaces-1/projects/Projects-1/triggers{?skip,take,partialName,triggerActionType,triggerActionCategory,runbooks}", "ScheduledTriggers": "/api/Spaces-1/projects/Projects-1/triggers/scheduled{?skip,take,partialName,ids}", "OrderChannels": "/api/Spaces-1/projects/Projects-1/channels/order", "Progression": "/api/Spaces-1/projects/Projects-1/progression{?releaseHistoryCount}", "RunbookTaskRunDashboardItemsTemplate": "/api/Spaces-1/progression/runbooks/taskRuns{?skip,take,ids,projectIds,runbookIds,environmentIds,tenantIds,taskIds}", "DeploymentProcess": "/api/Spaces-1/projects/Projects-1/deploymentprocesses", "DeploymentSettings": "/api/Spaces-1/projects/Projects-1/deploymentsettings", "Web": "/app#/Spaces-1/projects/Projects-1", "Logo": "/api/Spaces-1/projects/Projects-1/logo?cb=map-signs-%235E2EA2-2024.3.10989", "Metadata": "/api/Spaces-1/projects/Projects-1/metadata", "Runbooks": "/api/Spaces-1/projects/Projects-1/runbooks{?skip,take,partialName}", "RunbookSnapshots": "/api/Spaces-1/projects/Projects-1/runbookSnapshots{/name}{?skip,take,searchByName}", "Summary": "/api/Spaces-1/projects/Projects-1/summary", "GitConnectionTest": "/api/Spaces-1/projects/Projects-1/git/connectivity-test", "InsightsMetrics": "/api/Spaces-1/projects/Projects-1/insights/metrics{?channelId,environmentId,tenantId,tenantFilter,timeRange,granularity,timeZone}", "GitCompatibilityReport": "/api/Spaces-1/projects/Projects-1/git/compatibility-report", "ConvertToGit": "/api/Spaces-1/projects/Projects-1/git/convert", "ConvertToVcs": "/api/Spaces-1/projects/Projects-1/git/convert" } } ``` Port entityJSON ``` { "identifier": "Projects-1", "title": "Sample Project", "icon": "Octopus", "blueprint": "octopusProject", "team": [], "properties": { "slug": null, "url": "https://testport.octopus.app/app#/Spaces-1/projects/Projects-1", "description": "Explore how to automate your deployments with Octopus Deploy.\n\nEdit the custom script step, tweak the variables, and run your first deployment. Experiment and make it your own!", "isDisabled": false, "tenantedDeploymentMode": "Untenanted" }, "relations": {}, "createdAt": "2024-08-22T14:27:37.814Z", "createdBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT", "updatedAt": "2024-08-22T14:27:37.814Z", "updatedBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT" } ``` Default mappingYAML ``` - kind: release selector: query: 'true' port: entity: mappings: identifier: .Id title: .ProjectId + "(" + .Version + ")" blueprint: '"octopusRelease"' properties: version: .Version assembledDate: .Assembled channelId: .ChannelId releaseNotes: .ReleaseNotes url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .SpaceId + "/releases/" + .Id relations: project: .ProjectId ``` Sample payloadJSON ``` { "Id": "Releases-44", "SpaceId": "Spaces-1", "ProjectId": "Projects-41", "Version": "0.0.2", "ChannelId": "Channels-41", "ReleaseNotes": null, "ProjectDeploymentProcessSnapshotId": "deploymentprocess-Projects-41-s-1-QXLY2", "IgnoreChannelRules": false, "BuildInformation": [], "Assembled": "2024-08-21T16:17:46.750+00:00", "LibraryVariableSetSnapshotIds": [], "SelectedPackages": [], "SelectedGitResources": [], "ProjectVariableSetSnapshotId": "variableset-Projects-41-s-0-S6LJL", "VersionControlReference": null, "Links": { "Self": "/api/Spaces-1/releases/Releases-44", "Project": "/api/Spaces-1/projects/Projects-41", "Channel": "/api/Spaces-1/projects/Projects-41/channels/Channels-41", "Progression": "/api/Spaces-1/releases/Releases-44/progression", "Deployments": "/api/Spaces-1/releases/Releases-44/deployments{?skip,take}", "DeploymentTemplate": "/api/Spaces-1/releases/Releases-44/deployments/template", "Artifacts": "/api/Spaces-1/artifacts?regarding=Releases-44", "ProjectVariableSnapshot": "/api/Spaces-1/variables/variableset-Projects-41-s-0-S6LJL", "ProjectDeploymentProcessSnapshot": "/api/Spaces-1/deploymentprocesses/deploymentprocess-Projects-41-s-1-QXLY2", "Web": "/app#/Spaces-1/releases/Releases-44", "SnapshotVariables": "/api/Spaces-1/releases/Releases-44/snapshot-variables", "Defects": "/api/Spaces-1/releases/Releases-44/defects", "ReportDefect": "/api/Spaces-1/releases/Releases-44/defects", "ResolveDefect": "/api/Spaces-1/releases/Releases-44/defects/resolve", "DeploymentPreviews": "/api/Spaces-1/releases/Releases-44/deployments/previews/", "Variables": "/api/Spaces-1/projects/Projects-41/releases/Releases-44/variables" } } ``` Port entityJSON ``` { "identifier": "Releases-44", "title": "Projects-41(0.0.2)", "icon": "Octopus", "blueprint": "octopusRelease", "team": [], "properties": { "version": "0.0.2", "assembledDate": "2024-08-21T16:17:46.750+00:00", "channelId": "Channels-41", "releaseNotes": null, "url": "https://testport.octopus.app/app#/Spaces-1/releases/Releases-44" }, "relations": {}, "createdAt": "2024-08-22T14:27:41.697Z", "createdBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT", "updatedAt": "2024-08-22T14:27:41.697Z", "updatedBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT" } ``` Default mappingYAML ``` - kind: deployment selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusDeployment"' properties: createdAt: .Created deployedBy: .DeployedBy taskId: .TaskId failureEncountered: .FailureEncountered comments: .Comments url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .SpaceId + "/deployments/" + .Id relations: release: .ReleaseId project: .ProjectId ``` Sample payloadJSON ``` { "Id": "Deployments-1", "SpaceId": "Spaces-1", "ReleaseId": "Releases-1", "ChannelId": "Channels-2", "DeploymentProcessId": "deploymentprocess-Projects-1-s-1-CGNSF", "Changes": [], "ChangesMarkdown": null, "EnvironmentId": "Environments-1", "TenantId": null, "ForcePackageDownload": false, "ForcePackageRedeployment": false, "Priority": "LifecycleDefault", "SkipActions": [], "SpecificMachineIds": [], "ExcludedMachineIds": [], "ManifestVariableSetId": null, "TaskId": "ServerTasks-11599", "ProjectId": "Projects-1", "UseGuidedFailure": false, "Comments": null, "FormValues": {}, "QueueTime": null, "QueueTimeExpiry": null, "Name": "Deploy to Development", "Created": "2024-08-02T05:07:42.445+00:00", "TentacleRetentionPeriod": null, "ChangeRequestSettings": null, "DeployedBy": "System", "DeployedById": null, "FailureEncountered": false, "DeployedToMachineIds": [], "ExecutionPlanLogContext": { "Steps": [] }, "Links": { "Self": "/api/Spaces-1/deployments/Deployments-1", "Release": "/api/Spaces-1/releases/Releases-1", "Environment": "/api/Spaces-1/environments/Environments-1", "Project": "/api/Spaces-1/projects/Projects-1", "Task": "/api/tasks/ServerTasks-11599", "Web": "/app#/Spaces-1/deployments/Deployments-1", "Artifacts": "/api/Spaces-1/artifacts?regarding=Deployments-1", "Interruptions": "/api/Spaces-1/interruptions?regarding=Deployments-1", "DeploymentProcess": "/api/Spaces-1/deploymentprocesses/deploymentprocess-Projects-1-s-1-CGNSF" } } ``` Port entityJSON ``` { "identifier": "Deployments-1", "title": "Deploy to Development", "icon": "Octopus", "blueprint": "octopusDeployment", "team": [], "properties": { "createdAt": "2024-08-02T05:07:42.445+00:00", "deployedBy": "System", "taskId": "ServerTasks-11599", "failureEncountered": false, "comments": null, "url": "https://testport.octopus.app/app#/Spaces-1/deployments/Deployments-1" }, "relations": {}, "createdAt": "2024-08-22T14:27:45.276Z", "createdBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT", "updatedAt": "2024-08-22T14:27:45.276Z", "updatedBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT" } ``` Default mappingYAML ``` - kind: machine selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusMachine"' properties: roles: .Roles status: .HealthStatus url: env.OCEAN__INTEGRATION__CONFIG__SERVER_URL + "/app#/" + .SpaceId + "/infrastructure/machines/" + .Id + "/settings" isDisabled: .IsDisabled operatingSystem: .OperatingSystem architecture: .Architecture statusSummary: .StatusSummary endpointType: .Endpoint.DeploymentTargetTypeId communicationStyle: .Endpoint.CommunicationStyle relations: space: .SpaceId ``` Sample payloadJSON ``` { "Id": "Machines-4", "EnvironmentIds": [ "Environments-1" ], "Roles": [ "test-server" ], "TenantedDeploymentParticipation": "Untenanted", "TenantIds": [], "TenantTags": [], "SpaceId": "Spaces-1", "Name": "ECS Instance Dev", "Thumbprint": null, "Uri": null, "IsDisabled": false, "MachinePolicyId": "MachinePolicies-1", "HealthStatus": "Unhealthy", "HasLatestCalamari": true, "StatusSummary": "There was a problem communicating with this machine (last checked: Tuesday, 27 August 2024 5:39:57 PM +00:00)", "IsInProcess": false, "Endpoint": { "CommunicationStyle": "StepPackage", "DeploymentTargetTypeId": "aws-ecs-target", "StepPackageId": "aws-ecs-target", "StepPackageVersion": "3.0.1", "LastSavedStepPackageVersion": "3.0.1", "Inputs": { "clusterName": " team-isaac", "region": "eu-west-1", "authentication": { "credentials": { "type": "account", "account": "Accounts-1" }, "role": { "type": "noAssumedRole" } } }, "DefaultWorkerPoolId": "", "Id": null, "LastModifiedOn": null, "LastModifiedBy": null, "Links": { "Logo": "/api/steps/deploymenttargets/aws-ecs-target/3.0.1/logo" } }, "OperatingSystem": "Unknown", "ShellName": "Unknown", "ShellVersion": "Unknown", "Architecture": "Unknown", "Slug": "ecs-instance-dev", "SkipInitialHealthCheck": false, "Links": { "Self": "/api/Spaces-1/machines/Machines-4", "Connection": "/api/Spaces-1/machines/Machines-4/connection", "TasksTemplate": "/api/Spaces-1/machines/Machines-4/tasks{?skip,take,type}", "SinglyScopedVariableDetails": "/api/Spaces-1/machines/Machines-4/singlyScopedVariableDetails" } } ``` Port entityJSON ``` { "identifier": "Machines-4", "title": "ECS Instance Dev", "icon": "Octopus", "blueprint": "octopusMachine", "team": [], "properties": { "roles": [ "test-server" ], "status": "Unhealthy", "url": "https://testport.octopus.app/app#/Spaces-1/infrastructure/machines/Machines-4/settings", "isDisabled": false, "operatingSystem": "Unknown", "architecture": "Unknown", "statusSummary": "There was a problem communicating with this machine (last checked: Monday, 26 August 2024 5:40:16 PM +00:00)", "endpointType": "aws-ecs-target", "communicationStyle": "StepPackage" }, "relations": {}, "createdAt": "2024-08-22T14:27:53.944Z", "createdBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT", "updatedAt": "2024-08-27T12:29:12.362Z", "updatedBy": "zhOWp1YybWfY12bAxNz3d1ByX18iA1yT" } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * Hosted by Port ### 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # Ingest additional resources In addition to the supported resources listed in the [integration page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/.md), other resources can be ingested, as described on this page. This page will help you understand what kind of Octopus Deploy resources are supported by the integration and how to map them into Port. ## Check if a resource is supported[​](#check-if-a-resource-is-supported "Direct link to Check if a resource is supported") The Octopus Deploy integration relies on Octopus' API-first architecture, which maintains a consistent pattern for most resources. This means that you can bring virtually any resource into Port, as long as it follows the standard API structure. Here’s how to determine if a resource is supported: 1. Visit the [Octopus Deploy API Swagger documentation](https://demo.octopus.com/swaggerui/index.html) to see the available resources and their API structure. 2. **Check the resource path**: Determine if the API of the resource you want to integrate follows the standard pattern **`GET /{spaceId}/{resources}`** * **If the resource follows this pattern**: Great! It can be integrated. * **If not**: Please contact us or contribute by [adding support](https://github.com/port-labs/ocean/blob/main/CONTRIBUTING.md) to [the integration](https://github.com/port-labs/ocean/tree/main/integrations/octopus) yourself. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party API into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform, and perform other operations on existing fields and values from the integration API. ged ### Mapping the Resource to Port[​](#mapping-the-resource-to-port "Direct link to Mapping the Resource to Port") After determining that the resource is supported by the API, you can map it to Port by following these steps: * **Define the resource kind**: The value of `kind` should be the value of the resource in the api pattern **`GET /{spaceId}/{resources}`** without the trailing `s`. For example, if API path is **`GET /{spaceId}/runbooks`**, the value of `kind` should be `runbook`. * **Define the selector**: The selector is a JQ query that filters the resources you want to ingest. If the query evaluates to `false`, the resource will be skipped. * **Define the properties**: The properties are the fields of the Port entity. You can map the resource fields to the entity properties using JQ queries. * **Define the blueprint**: The blueprint is the name of the Port entity blueprint that you want to use for the resource. * **Define the relations**: You can define `relations` between the resource and other resources in Port by mapping the related fields. ## Example configurations[​](#example-configurations "Direct link to Example configurations") Here are some examples of how to expand the integration to include additional resources ### Runbook[​](#runbook "Direct link to Runbook") **Runbook Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusRunbook", "title": "Octopus Runbook", "icon": "Octopus", "description": "A runbook in Octopus Deploy", "schema": { "properties": { "description": { "type": "string", "title": "Description", "description": "The description of the runbook" }, "lastModifiedOn": { "type": "string", "title": "Last Modified On", "format": "date-time", "description": "The date and time when the runbook was last modified" }, "environmentScope": { "type": "string", "title": "Environment Scope", "description": "The scope of environments for the runbook" }, "environments": { "type": "array", "items": { "type": "string" }, "title": "Environments", "description": "The environments associated with the runbook" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } }, ``` **Mapping configuration for Runbook (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: runbook selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusRunbook"' properties: description: .Description lastModifiedOn: .LastModifiedOn environmentScope: .EnvironmentScope environments: .Environments relations: space: .SpaceId ``` ### Task[​](#task "Direct link to Task") **Task Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusTask", "title": "Octopus Task", "icon": "Octopus", "description": "A task in Octopus Deploy", "schema": { "properties": { "description": { "type": "string", "title": "Description", "description": "The description of the task" }, "state": { "type": "string", "title": "State", "description": "The current state of the task" }, "isCompleted": { "type": "boolean", "title": "Is Completed", "description": "Indicates if the task is completed" }, "completedTime": { "type": "string", "title": "Completed Time", "format": "date-time", "description": "The time when the task was completed" }, "hasWarningsOrErrors": { "type": "boolean", "title": "Has Warnings or Errors", "description": "Indicates if the task encountered warnings or errors" }, "finishedSuccessfully": { "type": "boolean", "title": "Finished Successfully", "description": "Indicates if the task finished successfully" }, "duration": { "type": "string", "title": "Duration", "description": "The duration of the task" }, "queueTime": { "type": "string", "title": "Queue Time", "format": "date-time", "description": "The time when the task was queued" }, "startTime": { "type": "string", "title": "Start Time", "format": "date-time", "description": "The time when the task started" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for Task (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: task selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusTask"' properties: description: .Description state: .State isCompleted: .IsCompleted completedTime: .CompletedTime hasWarningsOrErrors: .HasWarningsOrErrors finishedSuccessfully: .FinishedSuccessfully duration: .Duration queueTime: .QueueTime startTime: .StartTime relations: space: .SpaceId ``` ### Team[​](#team "Direct link to Team") **Team Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusTeam", "title": "Octopus Team", "icon": "Octopus", "description": "A team in Octopus Deploy", "schema": { "properties": { "description": { "type": "string", "title": "Description", "description": "The description of the team" }, "canBeDeleted": { "type": "boolean", "title": "Can Be Deleted", "description": "Indicates if the team can be deleted" }, "canBeRenamed": { "type": "boolean", "title": "Can Be Renamed", "description": "Indicates if the team can be renamed" }, "canChangeMembers": { "type": "boolean", "title": "Can Change Members", "description": "Indicates if the team members can be changed" }, "canChangeRoles": { "type": "boolean", "title": "Can Change Roles", "description": "Indicates if the team roles can be changed" }, "memberUserIds": { "type": "array", "items": { "type": "string" }, "title": "Member User IDs", "description": "The IDs of the users who are members of the team" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for Team (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: team selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusTeam"' properties: description: .Description canBeDeleted: .CanBeDeleted canBeRenamed: .CanBeRenamed canChangeMembers: .CanChangeMembers canChangeRoles: .CanChangeRoles memberUserIds: .MemberUserIds relations: space: .SpaceId ``` ### WorkerPool[​](#workerpool "Direct link to WorkerPool") **WorkerPool Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusWorkerPool", "title": "Octopus Worker Pool", "icon": "Octopus", "description": "A worker pool in Octopus Deploy", "schema": { "properties": { "workerPoolType": { "type": "string", "title": "Worker Pool Type", "description": "The type of the worker pool (e.g., StaticWorkerPool)" }, "isDefault": { "type": "boolean", "title": "Is Default", "description": "Indicates if the worker pool is the default" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for WorkerPool (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: workerpool selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusWorkerPool"' properties: workerPoolType: .WorkerPoolType isDefault: .IsDefault relations: space: .SpaceId ``` ### Worker[​](#worker "Direct link to Worker") **Worker Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusWorker", "title": "Octopus Worker", "icon": "Octopus", "description": "A worker in Octopus Deploy", "schema": { "properties": { "operatingSystem": { "type": "string", "title": "Operating System", "description": "The operating system of the worker" }, "healthStatus": { "type": "string", "title": "Health Status", "description": "The health status of the worker" }, "isDisabled": { "type": "boolean", "title": "Is Disabled", "description": "Indicates if the worker is disabled" }, "architecture": { "type": "string", "title": "Architecture", "description": "The architecture of the worker" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for Team (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: worker selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusWorker"' properties: operatingSystem: .OperatingSystem healthStatus: .HealthStatus isDisabled: .IsDisabled architecture: .Architecture relations: space: .SpaceId ``` ### Environment[​](#environment "Direct link to Environment") **Environment Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusEnvironment", "title": "Octopus Environment", "icon": "Octopus", "description": "An environment in Octopus Deploy", "schema": { "properties": { "description": { "type": "string", "title": "Description", "description": "The description of the environment" }, "allowDynamicInfrastructure": { "type": "boolean", "title": "Allow Dynamic Infrastructure", "description": "Indicates if dynamic infrastructure is allowed in this environment" }, "useGuidedFailure": { "type": "boolean", "title": "Use Guided Failure", "description": "Indicates if guided failure mode is enabled for this environment" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for Environment (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: environment selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusEnvironment"' properties: description: .Description allowDynamicInfrastructure: .AllowDynamicInfrastructure useGuidedFailure: .UseGuidedFailure relations: space: .SpaceId ``` ### Proxy[​](#proxy "Direct link to Proxy") **Proxy Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusProxy", "title": "Octopus Proxy", "icon": "Octopus", "description": "A proxy configuration in Octopus Deploy", "schema": { "properties": { "host": { "type": "string", "title": "Host", "description": "The hostname of the proxy" }, "port": { "type": "integer", "title": "Port", "description": "The port used by the proxy" }, "username": { "type": "string", "title": "Username", "description": "The username for authentication with the proxy" }, "proxyType": { "type": "string", "title": "Proxy Type", "description": "The type of the proxy" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for Proxy (click to expand)** Kind value The value of `kind` should be `proxie` for Proxy resources. This is because of the pattern of the API endpoints discussed in the [mapping the resource to port](#mapping-the-resource-to-port) section. ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: proxie selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusProxy"' properties: host: .Host port: .Port username: .Username proxyType: .ProxyType relations: space: .SpaceId ``` ### Account[​](#account "Direct link to Account") **Account Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusAccount", "title": "Octopus Account", "icon": "Octopus", "description": "An account configuration in Octopus Deploy", "schema": { "properties": { "accountType": { "type": "string", "title": "Account Type", "description": "The type of the account, e.g., AmazonWebServicesAccount" }, "description": { "type": "string", "title": "Description", "description": "The description of the account" }, "environmentIds": { "type": "array", "items": { "type": "string" }, "title": "Environment IDs", "description": "IDs of the environments associated with the account" }, "tenantedDeploymentParticipation": { "type": "string", "title": "Tenanted Deployment Participation", "description": "Indicates the participation of the account in tenanted deployments" }, "tenantIds": { "type": "array", "items": { "type": "string" }, "title": "Tenant IDs", "description": "IDs of the tenants associated with the account" }, "tenantTags": { "type": "array", "items": { "type": "string" }, "title": "Tenant Tags", "description": "Tags of tenants associated with the account" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for Account (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: account selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusAccount"' properties: accountType: .AccountType description: .Description environmentIds: .EnvironmentIds tenantedDeploymentParticipation: .TenantedDeploymentParticipation tenantIds: .TenantIds tenantTags: .TenantTags relations: space: .SpaceId ``` ### Package[​](#package "Direct link to Package") **Package Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusPackage", "title": "Octopus Package", "icon": "Octopus", "description": "A package stored in Octopus Deploy", "schema": { "properties": { "description": { "type": "string", "title": "Description", "description": "The description of the package" }, "feedId": { "type": "string", "title": "Feed ID", "description": "ID of the feed associated with the package" }, "fileExtension": { "type": "string", "title": "File Extension", "description": "The file extension of the package" }, "version": { "type": "string", "title": "Version", "description": "The version of the package" }, "hash": { "type": "string", "title": "Hash", "description": "The hash of the package for verification" }, "packageSizeBytes": { "type": "integer", "title": "Package Size (Bytes)", "description": "The size of the package in bytes" }, "published": { "type": "string", "title": "Published Date", "format": "date-time", "description": "The date the package was published" }, "releaseNotes": { "type": "string", "title": "Release Notes", "description": "Notes provided for the release of the package" }, "summary": { "type": "string", "title": "Summary", "description": "A summary of the package" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for Package (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: package selector: query: "true" port: entity: mappings: identifier: .Id title: .Title blueprint: '"octopusPackage"' properties: description: .Description feedId: .FeedId fileExtension: .FileExtension version: .Version hash: .Hash packageSizeBytes: .PackageSizeBytes published: .Published releaseNotes: .ReleaseNotes summary: .Summary relations: space: .SpaceId ``` ### Subscription[​](#subscription "Direct link to Subscription") **Subscription Blueprint (click to expand)** Create in Port ``` { "identifier": "octopusSubscription", "title": "Octopus Subscription", "icon": "Octopus", "description": "An event notification subscription in Octopus Deploy", "schema": { "properties": { "name": { "type": "string", "title": "Name", "description": "The name of the subscription" }, "type": { "type": "string", "title": "Type", "description": "The type of the subscription" }, "isDisabled": { "type": "boolean", "title": "Is Disabled", "description": "Indicates if the subscription is disabled" }, "emailFrequencyPeriod": { "type": "string", "title": "Email Frequency Period", "description": "The frequency at which email notifications are sent" }, "emailPriority": { "type": "string", "title": "Email Priority", "description": "The priority of email notifications" }, "webhookURI": { "type": "string", "title": "Webhook URI", "description": "The URI for the webhook" }, "webhookTimeout": { "type": "string", "title": "Webhook Timeout", "description": "The timeout setting for the webhook" }, "lastModifiedBy": { "type": "string", "title": "Last Modified By", "description": "The user who last modified the subscription" }, "lastModifiedOn": { "type": "string", "title": "Last Modified On", "format": "date-time", "description": "The date and time when the subscription was last modified" } } }, "calculationProperties": {}, "relations": { "space": { "title": "Space", "description": "The space to which this project belongs", "target": "octopusSpace", "required": false, "many": false } } } ``` **Mapping configuration for Subscription (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: subscription selector: query: "true" port: entity: mappings: identifier: .Id title: .Name blueprint: '"octopusSubscription"' properties: type: .Type isDisabled: .IsDisabled emailFrequencyPeriod: .EventNotificationSubscription.EmailFrequencyPeriod emailPriority: .EventNotificationSubscription.EmailPriority webhookURI: .EventNotificationSubscription.WebhookURI webhookTimeout: .EventNotificationSubscription.WebhookTimeout lastModifiedBy: .LastModifiedBy lastModifiedOn: .LastModifiedOn relations: space: .SpaceId ``` --- # Cloud cost Port's cloud cost integrations make it easy to keep your software catalog up to date based on information stored in your preferred cloud cost tool. For more information, check out the documentation for your respective tool: ## [🗃Kubecost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/.md) ## [📄️OpenCost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/opencost/.md) [Sync OpenCost Kubernetes cost and resource spend data into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/opencost/.md) --- # Kubecost ![Kubecost icon](/img/guides/icons/Kubecost.svg)![Kubecost icon](/img/guides/icons/Kubecost.svg) Loading version... Port's Kubecost integration allows you to model Kubecost resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[kubesystem](https://docs.kubecost.com/apis/monitoring-apis/api-allocation#allocation-api)[cloud](https://docs.kubecost.com/apis/monitoring-apis/cloud-cost-api#cloud-cost-querying-api) ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: kubesystem selector: query: 'true' port: entity: mappings: blueprint: '"kubecostResourceAllocation"' identifier: .name title: .name properties: cluster: .properties.cluster namespace: .properties.namespace startDate: .start endDate: .end cpuCoreHours: .cpuCoreHours cpuCost: .cpuCost cpuEfficiency: .cpuEfficiency gpuHours: .gpuHours gpuCost: .gpuCost networkCost: .networkCost loadBalancerCost: .loadBalancerCost pvCost: .pvCost pvBytes: .pvBytes ramBytes: .ramBytes ramCost: .ramCost ramEfficiency: .ramEfficiency sharedCost: .sharedCost externalCost: .externalCost totalCost: .totalCost totalEfficiency: .totalEfficiency - kind: cloud selector: query: 'true' port: entity: mappings: blueprint: '"kubecostCloudAllocation"' identifier: .properties.provider + "/" + .properties.providerID + "/" + .properties.category + "/" + .properties.service | gsub("[^A-Za-z0-9@_.:\\\\/=-]"; "-") title: .properties.provider + "/" + .properties.service properties: provider: .properties.provider accountID: .properties.accountID invoiceEntityID: .properties.invoiceEntityID startDate: .window.start endDate: .window.end listCost: .listCost.cost listCostPercent: .listCost.kubernetesPercent netCost: .netCost.cost netCostPercent: .netCost.kubernetesPercent amortizedNetCost: .amortizedNetCost.cost amortizedNetCostPercent: .amortizedNetCost.kubernetesPercent invoicedCost: .invoicedCost.cost invoicedCostPercent: .invoicedCost.kubernetesPercent ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") kubesystemcloud Default mappingYAML ``` - kind: kubesystem selector: query: 'true' port: entity: mappings: blueprint: '"kubecostResourceAllocation"' identifier: .name title: .name properties: cluster: .properties.cluster namespace: .properties.namespace startDate: .start endDate: .end cpuCoreHours: .cpuCoreHours cpuCost: .cpuCost cpuEfficiency: .cpuEfficiency gpuHours: .gpuHours gpuCost: .gpuCost networkCost: .networkCost loadBalancerCost: .loadBalancerCost pvCost: .pvCost pvBytes: .pvBytes ramBytes: .ramBytes ramCost: .ramCost ramEfficiency: .ramEfficiency sharedCost: .sharedCost externalCost: .externalCost totalCost: .totalCost totalEfficiency: .totalEfficiency ``` Sample payloadJSON ``` { "name": "argocd", "properties": { "cluster": "cluster-one", "node": "gke-my-regional-cluster-default-pool-e8093bfa-0bjg", "namespace": "argocd", "providerID": "gke-my-regional-cluster-default-pool-e8093bfa-0bjg", "namespaceLabels": { "kubernetes_io_metadata_name": "argocd" } }, "window": { "start": "2023-10-30T00:00:00Z", "end": "2023-10-30T01:00:00Z" }, "start": "2023-10-30T00:00:00Z", "end": "2023-10-30T01:00:00Z", "minutes": 60, "cpuCores": 0.00515, "cpuCoreRequestAverage": 0, "cpuCoreUsageAverage": 0.00514, "cpuCoreHours": 0.00515, "cpuCost": 0.00012, "cpuCostAdjustment": 0, "cpuEfficiency": 1, "gpuCount": 0, "gpuHours": 0, "gpuCost": 0, "gpuCostAdjustment": 0, "networkTransferBytes": 2100541.53, "networkReceiveBytes": 2077024.88318, "networkCost": 0, "networkCrossZoneCost": 0, "networkCrossRegionCost": 0, "networkInternetCost": 0, "networkCostAdjustment": 0, "loadBalancerCost": 0.02708, "loadBalancerCostAdjustment": 0, "pvBytes": 0, "pvByteHours": 0, "pvCost": 0, "pvs": "None", "pvCostAdjustment": 0, "ramBytes": 135396181.33333, "ramByteRequestAverage": 0, "ramByteUsageAverage": 135394433.70477, "ramByteHours": 135396181.33333, "ramCost": 0.00041, "ramCostAdjustment": 0, "ramEfficiency": 1, "externalCost": 0, "sharedCost": 0, "totalCost": 0.02761, "totalEfficiency": 1, "proportionalAssetResourceCosts": {}, "lbAllocations": { "cluster-one/argocd/argocd-server": { "service": "argocd/argocd-server", "cost": 0.027083333333333334, "private": false, "ip": "" } }, "sharedCostBreakdown": {} } ``` Port entityJSON ``` { "identifier": "argocd", "title": "argocd", "icon": "Cluster", "blueprint": "kubecostResourceAllocation", "team": [], "properties": { "cluster": "cluster-one", "namespace": "argocd", "startDate": "2023-10-30T04:00:00.000Z", "endDate": "2023-10-30T05:00:00.000Z", "cpuCoreHours": 0.0051, "cpuCost": 0.00012, "cpuEfficiency": 1, "gpuHours": 0, "gpuCost": 0, "networkCost": 0, "loadBalancerCost": 0.02708, "pvCost": 0, "pvBytes": 0, "ramBytes": 135396181.33333, "ramCost": 0.00041, "ramEfficiency": 1, "sharedCost": 0, "externalCost": 0, "totalCost": 0.02761, "totalEfficiency": 1 }, "relations": {}, "createdAt": "2023-10-30T13:25:42.717Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-10-30T13:28:37.379Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ---------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `window` | `string` | The time window for the query. Supported values: preset windows ('today', 'week', 'month', 'yesterday', 'lastweek', 'lastmonth', '30m', '12h', '7d'), an ISO8601 UTC date-time pair ('2026-03-30T00:00:00Z,2026-03-31T00:00:00Z'), or a Unix timestamp pair ('1711756800,1711843200'). Default: `"today"`. | | `aggregate` | `string` | Field by which to aggregate the results. E.g. 'namespace', 'service', or 'label:app'. | | `step` | `string` | Granularity of each time bucket in the results. For example, '1d' breaks a week-long window into daily buckets. Accepts seconds (s), minutes (m), hours (h), or days (d), e.g. '30m', '2h', '1d'. Defaults to the full window as a single bucket. | | `accumulate` | `boolean` | If true, sum the entire range of sets into a single set. Default: `false`. | | `idle` | `boolean` | If true, include idle cost (i.e. the cost of the un-allocated assets) as its own allocation Default: `true`. | | `external` | `boolean` | If true, include external, or out-of-cluster costs in each allocation. Default is false. Default: `false`. | | `filterClusters` | `string` | Comma-separated list of clusters to match; e.g. cluster-one,cluster-two will return results from only those two clusters. | | `filterNodes` | `string` | Comma-separated list of nodes to match; e.g. node-one,node-two will return results from only those two nodes. | | `filterNamespaces` | `string` | Comma-separated list of namespaces to match; e.g. namespace-one,namespace-two will return results from only those two namespaces. | | `filterControllerKinds` | `string` | Comma-separated list of controller kinds to match; e.g. deployment, job will return results with only those two controller kinds. | | `filterControllers` | `string` | Comma-separated list of controllers to match; e.g. deployment-one,statefulset-two will return results from only those two controllers. | | `filterPods` | `string` | Comma-separated list of pods to match; e.g. pod-one,pod-two will return results from only those two pods. | | `filterAnnotations` | `string` | Comma-separated list of annotations to match; e.g. name:annotation-one,name:annotation-two will return results with either of those two annotation key-value-pairs. | | `filterLabels` | `string` | Comma-separated list of annotations to match; e.g. app:cost-analyzer, app:prometheus will return results with either of those two label key-value-pairs. | | `filterServices` | `string` | Comma-separated list of services to match; e.g. frontend-one,frontend-two will return results with either of those two services | | `shareIdle` | `boolean` | If true, idle cost is allocated proportionally across all non-idle allocations, per-resource. That is, idle CPU cost is shared with each non-idle allocation's CPU cost, according to the percentage of the total CPU cost represented. Default is false Default: `false`. | | `splitIdle` | `boolean` | If true, and shareIdle == false, Idle Allocations are created on a per cluster or per node basis rather than being aggregated into a single idle allocation. Default is false Default: `false`. | | `idleByNode` | `boolean` | f true, idle allocations are created on a per node basis. Which will result in different values when shared and more idle allocations when split. Default is false. Default: `false`. | | `shareNamespaces` | `string` | Comma-separated list of namespaces to share; e.g. kube-system, kubecost will share the costs of those two namespaces with the remaining non-idle, unshared allocations. | | `shareLabels` | `string` | Comma-separated list of labels to share; e.g. env:staging, app:test will share the costs of those two label values with the remaining non-idle, unshared allocations. | | `shareCost` | `number` | Floating-point value representing a monthly cost to share with the remaining non-idle, unshared allocations; e.g. 30.42 ($1.00/day == $30.42/month) for the query yesterday (1 day) will split and distribute exactly $1.00 across the allocations. Default is 0.0. Default: `0`. | | `offset` | `integer` | Number of items to skip before starting to collect the result set. | | `limit` | `integer` | Maximum number of items to return in the result set. | | `filter` | `string` | Filter results by any category which that can be aggregated by, can support multiple filterable items in the same category in a comma-separated list. E.g. 'namespace:kube-system,kubecost'. | | `format` | `string` | Format of the output. Default is JSON. | | `costMetric` | `string` | Cost metric format. Default: `"cummulative"`. | | `includeSharedCostBreakdown` | `boolean` | If true, the cost breakdown for shared costs is included in the response. Default is false. Default: `true`. | | `reconcile` | `boolean` | If true, pulls data from the Assets cache and corrects prices of Allocations according to their related Assets Default: `true`. | | `shareTenancyCosts` | `boolean` | If true, share the cost of cluster overhead assets such as cluster management costs and node attached volumes across tenants of those resources. Default: `true`. | | `shareSplit` | `string` | Determines how to split shared costs among non-idle, unshared allocations. Default: `"weighted"`. | BlueprintJSON Create in Port ``` { "identifier": "kubecostCloudAllocation", "description": "This blueprint represents an Kubecost cloud resource allocation in our software catalog", "title": "Kubecost Cloud Allocation", "icon": "Cluster", "schema": { "properties": { "provider": { "type": "string", "title": "Provider" }, "accountID": { "type": "string", "title": "Account ID" }, "invoiceEntityID": { "type": "string", "title": "Invoice Entity ID" }, "startDate": { "title": "Start Date", "type": "string", "format": "date-time" }, "endDate": { "title": "End Date", "type": "string", "format": "date-time" }, "listCost": { "title": "List Cost Value", "type": "number" }, "listCostPercent": { "title": "List Cost Percent", "type": "number" }, "netCost": { "title": "Net Cost Value", "type": "number" }, "netCostPercent": { "title": "Net Cost Percent", "type": "number" }, "amortizedNetCost": { "title": "Amortized Net Cost", "type": "number" }, "amortizedNetCostPercent": { "title": "Amortized Net Cost Percent", "type": "number" }, "invoicedCost": { "title": "Invoice Cost", "type": "number" }, "invoicedCostPercent": { "title": "Invoice Cost Percent", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: cloud selector: query: 'true' port: entity: mappings: blueprint: '"kubecostCloudAllocation"' identifier: .properties.provider + "/" + .properties.providerID + "/" + .properties.category + "/" + .properties.service | gsub("[^A-Za-z0-9@_.:\\\\/=-]"; "-") title: .properties.provider + "/" + .properties.service properties: provider: .properties.provider accountID: .properties.accountID invoiceEntityID: .properties.invoiceEntityID startDate: .window.start endDate: .window.end listCost: .listCost.cost listCostPercent: .listCost.kubernetesPercent netCost: .netCost.cost netCostPercent: .netCost.kubernetesPercent amortizedNetCost: .amortizedNetCost.cost amortizedNetCostPercent: .amortizedNetCost.kubernetesPercent invoicedCost: .invoicedCost.cost invoicedCostPercent: .invoicedCost.kubernetesPercent ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `window` | `string` | The time window for the query. Supported values: preset windows ('today', 'week', 'month', 'yesterday', 'lastweek', 'lastmonth', '30m', '12h', '7d'), an ISO8601 UTC date-time pair ('2026-03-30T00:00:00Z,2026-03-31T00:00:00Z'), or a Unix timestamp pair ('1711756800,1711843200'). Default: `"today"`. | | `aggregate` | `string` | Field by which to aggregate the results. E.g. 'namespace', 'service', or 'label:app'. | | `filterInvoiceEntityIDs` | `string` | GCP only, filter for projectID | | `filterAccountIDs` | `string` | Filter for account | | `filterProviders` | `string` | Filter for cloud service provider | | `filterLabel` | `string` | Filter for a specific label. Does not support filtering for multiple labels at once. | | `filterServices` | `string` | Comma-separated list of services to match; e.g. frontend-one,frontend-two will return results with either of those two services | | `accumulate` | `boolean` | If true, sum the entire range of sets into a single set. Default: `false`. | | `offset` | `integer` | Number of items to skip before starting to collect the result set. | | `limit` | `integer` | Maximum number of items to return in the result set. | | `filter` | `string` | Filter results by any category which that can be aggregated by, can support multiple filterable items in the same category in a comma-separated list. E.g. 'namespace:kube-system,kubecost'. | ## Advanced[​](#advanced "Direct link to Advanced") For advanced configuration including customizing how Kubecost kinds are ingested, read the [advanced section of Kubecost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/advanced/.md) guide. --- # Advanced configuration for Kubecost objects This guide provides information for configuring how Kubecost objects are ingested into your Kubecost installation. The Kubecost integration uses a YAML configuration to describe the process of loading data into the developer portal. Each kind has specific configuration for both the v1 and v2 of the Kubecost API ## Available configuration for `kubesystem` kind (v1)[​](#available-configuration-for-kubesystem-kind-v1 "Direct link to available-configuration-for-kubesystem-kind-v1") * **window** - Duration of time over which to query. Accepts: words like `today`, `week`, `month`, `yesterday`, `lastweek`, `lastmonth`; durations like `30m`, `12h`, `7d`; RFC3339 date pairs like `2021-01-02T15:04:05Z,2021-02-02T15:04:05Z`; Unix timestamps like `1578002645,1580681045`. * **aggregate** - Field by which to aggregate the results. Accepts: `cluster`, `node`, `namespace`, `controllerKind`, `controller`, `service`, `pod`, `container`, `label:name`, and `annotation:name`. Also accepts comma-separated lists for multi-aggregation, like `namespace,label:app`. * **step** - Duration of a single allocation set. If unspecified, this defaults to the window, so that you receive exactly one set for the entire window. If specified, such as `30m`, `2h`, `1d` etc, it works chronologically backward, querying in durations of step until the full window is covered. Default is `window`. * **accumulate** - If true, sum the entire range of sets into a single set. Default value is `false`. * **idle** - If true, include idle cost (i.e. the cost of the un-allocated assets) as its own allocation. Default is `true`. * **external** - If true, include external, or out-of-cluster costs in each allocation. Default is `false`. * **filterClusters** - Comma-separated list of clusters to match; e.g. `cluster-one,cluster-two` will return results from only those two clusters. * **filterNodes** - Comma-separated list of nodes to match; e.g. `node-one,node-two` will return results from only those two nodes. * **filterNamespaces** - Comma-separated list of namespaces to match; e.g. `namespace-one,namespace-two` will return results from only those two namespaces. * **filterControllerKinds** - Comma-separated list of controller kinds to match; e.g. `deployment`, job will return results with only those two controller kinds. * **filterControllers** - Comma-separated list of controllers to match; e.g. `deployment-one,statefulset-two` will return results from only those two controllers. * **filterPods** - Comma-separated list of pods to match; e.g. `pod-one,pod-two` will return results from only those two pods. * **filterAnnotations** - Comma-separated list of annotations to match; e.g. `name:annotation-one,name:annotation-two` will return results with either of those two annotation key-value-pairs. * **filterControllerKinds** - Comma-separated list of controller kinds to match; e.g. `deployment`, job will return results with only those two controller kinds. * **filterLabels** - Comma-separated list of annotations to match; e.g. `app:cost-analyzer, app:prometheus` will return results with either of those two label key-value-pairs. * **filterServices** - Comma-separated list of services to match; e.g. `frontend-one,frontend-two` will return results with either of those two services. * **shareIdle** - If true, idle cost is allocated proportionally across all non-idle allocations, per-resource. That is, idle CPU cost is shared with each non-idle allocation's CPU cost, according to the percentage of the total CPU cost represented. Default is `false`. * **splitIdle** - If true, and shareIdle == false, Idle Allocations are created on a per cluster or per node basis rather than being aggregated into a single idle allocation. Default is `false`. * **idleByNode** - If true, idle allocations are created on a per node basis. Which will result in different values when shared and more idle allocations when split. Default is `false`. * And any query parameter that could be found in the [Kubecost allocation API](https://docs.kubecost.com/apis/apis-overview/api-allocation#allocation-api) and [Kubecost Cloud API](https://docs.kubecost.com/apis/apis-overview/cloud-cost-api#cloud-cost-aggregate-api) **Mapping config example for v1 `kubesystem` kind (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: kubesystem selector: query: "true" window: "7d" aggregate: service step: "1d" # this will result in 7 entries which is 1 day over the course of `window` time period: 7 days accumulate: false idle: true external: false filterLabels: "app:internal-service" filterServices: "notification,account,functions" port: entity: mappings: blueprint: '"kubecostResourceAllocation"' identifier: .name title: .name properties: cluster: .properties.cluster namespace: .properties.namespace startDate: .start endDate: .end cpuCoreHours: .cpuCoreHours cpuCost: .cpuCost cpuEfficiency: .cpuEfficiency gpuHours: .gpuHours gpuCost: .gpuCost networkCost: .networkCost loadBalancerCost: .loadBalancerCost pvCost: .pvCost pvBytes: .pvBytes ramBytes: .ramBytes ramCost: .ramCost ramEfficiency: .ramEfficiency sharedCost: .sharedCost externalCost: .externalCost totalCost: .totalCost totalEfficiency: .totalEfficiency ``` ## Available configuration for `kubesystem` kind (v2)[​](#available-configuration-for-kubesystem-kind-v2 "Direct link to available-configuration-for-kubesystem-kind-v2") * **window** - Duration of time over which to query. Accepts: words like `today`, `week`, `month`, `yesterday`, `lastweek`, `lastmonth`; durations like `30m`, `12h`, `7d`; RFC3339 date pairs like `2021-01-02T15:04:05Z,2021-02-02T15:04:05Z`; Unix timestamps like `1578002645,1580681045`. * **aggregate** - Field by which to aggregate the results. Accepts: `cluster`, `node`, `namespace`, `controllerKind`, `controller`, `service`, `pod`, `container`, `label:name`, and `annotation:name`. Also accepts comma-separated lists for multi-aggregation, like `namespace,label:app`. * **step** - Duration of a single allocation set. If unspecified, this defaults to the window, so that you receive exactly one set for the entire window. If specified, such as `30m`, `2h`, `1d` etc, it works chronologically backward, querying in durations of step until the full window is covered. Default is `window`. * **accumulate** - If true, sum the entire range of sets into a single set. Default value is `false`. * **idle** - If true, include idle cost (i.e. the cost of the un-allocated assets) as its own allocation. Default is `true`. * **external** - If true, include external, or out-of-cluster costs in each allocation. Default is `false`. * **offset** - Refers to the number of line items you are offsetting. Pairs with limit. See [Kubecost docs on Using offset and limit parameters to parse payload results](https://docs.kubecost.com/apis/apis-overview#using-offset-and-limit-parameters-to-parse-payload-results) for more * **limit** - Refers to the number of line items per page. Pair with the offset parameter to filter your payload to specific pages of line items. * **filter** - Filter your results by any category which you can aggregate by, can support multiple filterable items in the same category in a comma-separated list. See the [Kubecost filter syntax guide](https://docs.kubecost.com/apis/filters-api) for more. * **format** - Set to `csv` to download an accumulated version of the allocation results in CSV format. Set to `pdf` to download an accumulated version of the allocation results in PDF format. By default, results will be in JSON format. * **costMetric** - Cost metric format. Learn about cost metric calculations in Kubecost Allocations Dashboard documentation. Possible values are `cumulative`, `hourly`, `daily`, and `monthly`. Default is `cumulative`. * **shareIdle** - If `true`, idle cost is allocated proportionally across all non-idle allocations, per-resource. That is, idle CPU cost is shared with each non-idle allocation's CPU cost, according to the percentage of the total CPU cost represented. Default is `false`. * **splitIdle** - If `true`, and `shareIdle == false`, idle allocations are created on a per cluster or per node basis rather than being aggregated into a single "idle" allocation. Default is `false`. * **idleByNode** - If `true`, idle allocations are created on a per node basis. Which will result in different values when shared and more idle allocations when split. `splitIdle` should only be configured to `true` when aggregate is configured to `node` or `cluster`. Default is `false`. * **includeSharedCostBreakdown** - Provides breakdown of cost metrics for any shared resources. Default is `true`. * **reconcile** - If `true`, pulls data from the Assets cache and corrects prices of Allocations according to their related Assets. The corrections from this process are stored in each cost category's cost adjustment field. If the integration with your cloud provider's billing data has been set up, this will result in the most accurate costs for Allocations. Default is `true`. * **shareTenancyCosts** - If `true`, share the cost of cluster overhead assets such as cluster management costs and node attached volumes across tenants of those resources. Results are added to the sharedCost field. Cluster management and attached volumes are shared by cluster. Default is `true`. * **shareNamespaces** - Comma-separated list of namespaces to share; e.g. `namespace-one,namespace-two` will return results from only those two namespaces. * **shareLabels** - Comma-separated list of labels to share; e.g. `env:staging`, `app:test` will share the costs of those two label values with the remaining non-idle, unshared allocations. * **shareCost** - Floating-point value representing a monthly cost to share with the remaining non-idle, unshared allocations; e.g. `30.42` ($1.00/day == $30.42/month) for the query `yesterday` (1 day) will split and distribute exactly $1.00 across the allocations. Default is `0.0`. * **shareSplit** - Determines how to split shared costs among non-idle, unshared allocations. By default, the split will be `weighted`; i.e. proportional to the cost of the allocation, relative to the total. The other option is `even`; i.e. each allocation gets an equal portion of the shared cost. Default is `weighted`. **Mapping config example for v2 `kubesystem` kind (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: kubesystem selector: query: "true" window: "7d" aggregate: service step: "1d" # this will result in 7 entries which is 1 day over the course of `window` time period: 7 days accumulate: false idle: true filter: 'labels:"app:internal-service","app:service-2"+service:"notification","account","functions"' # replaces all `filter*` properties needed in v1 port: entity: mappings: blueprint: '"kubecostResourceAllocation"' identifier: .name title: .name properties: cluster: .properties.cluster namespace: .properties.namespace startDate: .start endDate: .end cpuCoreHours: .cpuCoreHours cpuCost: .cpuCost cpuEfficiency: .cpuEfficiency gpuHours: .gpuHours gpuCost: .gpuCost networkCost: .networkCost loadBalancerCost: .loadBalancerCost pvCost: .pvCost pvBytes: .pvBytes ramBytes: .ramBytes ramCost: .ramCost ramEfficiency: .ramEfficiency sharedCost: .sharedCost externalCost: .externalCost totalCost: .totalCost totalEfficiency: .totalEfficiency ``` ## Available configuration for `cloud` kind (v1)[​](#available-configuration-for-cloud-kind-v1 "Direct link to available-configuration-for-cloud-kind-v1") * **window** - Duration of time over which to query. Accepts: words like `today`, `week`, `month`, `yesterday`, `lastweek`, `lastmonth`; durations like `30m`, `12h`, `7d`; RFC3339 date pairs like `2021-01-02T15:04:05Z,2021-02-02T15:04:05Z`; Unix timestamps like `1578002645,1580681045`. * **aggregate** - Field by which to aggregate the results. Accepts: invoiceEntityID, accountID, provider, service, and label:\. Supports multi-aggregation using comma-separated lists. Example: `accountID,service`, `namespace,label:app`. * **filterInvoiceEntityIDs** - Filter for account * **filterAccountIDs** - GCP only, filter for projectID * **filterProviders** - Filter for cloud service provider * **filterLabel** - Filter for a specific label. Does not support filtering for multiple labels at once. * **filterServices** - Comma-separated list of services to match; e.g. `frontend-one,frontend-two` will return results with either of those two services. **Mapping config example for v1 `kubecostCloudAllocation` kind (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: cloud selector: query: "true" window: 7d aggregate: service filterAccountIDs: "984573291056,374859201234" filterLabel: label-one,label-two filterServices: notification,account,chat port: entity: mappings: blueprint: '"kubecostCloudAllocation"' identifier: .properties.provider + "/" + .properties.providerID + "/" + .properties.category + "/" + .properties.service | gsub("[^A-Za-z0-9@_.:\\\\/=-]"; "-") title: .properties.provider + "/" + .properties.service properties: provider: .properties.provider accountID: .properties.accountID invoiceEntityID: .properties.invoiceEntityID startDate: .window.start endDate: .window.end listCost: .listCost.cost listCostPercent: .listCost.kubernetesPercent netCost: .netCost.cost netCostPercent: .netCost.kubernetesPercent amortizedNetCost: .amortizedNetCost.cost amortizedNetCostPercent: .amortizedNetCost.kubernetesPercent invoicedCost: .invoicedCost.cost invoicedCostPercent: .invoicedCost.kubernetesPercent ``` ## Available configuration for `cloud` kind (v2)[​](#available-configuration-for-cloud-kind-v2 "Direct link to available-configuration-for-cloud-kind-v2") * **window** - Duration of time over which to query. Accepts: words like `today`, `week`, `month`, `yesterday`, `lastweek`, `lastmonth`; durations like `30m`, `12h`, `7d`; RFC3339 date pairs like `2021-01-02T15:04:05Z,2021-02-02T15:04:05Z`; Unix timestamps like `1578002645,1580681045`. * **aggregate** - Field by which to aggregate the results. Accepts: invoiceEntityID, accountID, provider, service, and label:\. Supports multi-aggregation using comma-separated lists. Example: `accountID,service`, `namespace,label:app`. * **accumulate** - If true, sum the entire range of sets into a single set. Default value is `false`. * **offset** - Refers to the number of line items you are offsetting. Pairs with limit. See [Kubecost docs on Using offset and limit parameters to parse payload results](https://docs.kubecost.com/apis/apis-overview#using-offset-and-limit-parameters-to-parse-payload-results) for more * **limit** - Refers to the number of line items per page. Pair with the offset parameter to filter your payload to specific pages of line items. * **filter** - Filter your results by any category which you can aggregate by, can support multiple filterable items in the same category in a comma-separated list. See the [Kubecost filter syntax guide](https://docs.kubecost.com/apis/filters-api) for more. **Mapping config example for v2 `kubecostCloudAllocation` kind (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: cloud selector: query: "true" window: 7d aggregate: service accumulate: false filter: 'labels:"app:internal-service","app:service-2"+service:"notification","account","functions"' # replaces all `filter*` properties needed in v1 port: entity: mappings: blueprint: '"kubecostCloudAllocation"' identifier: .properties.provider + "/" + .properties.providerID + "/" + .properties.category + "/" + .properties.service | gsub("[^A-Za-z0-9@_.:\\\\/=-]"; "-") title: .properties.provider + "/" + .properties.service properties: provider: .properties.provider accountID: .properties.accountID invoiceEntityID: .properties.invoiceEntityID startDate: .window.start endDate: .window.end listCost: .listCost.cost listCostPercent: .listCost.kubernetesPercent netCost: .netCost.cost netCostPercent: .netCost.kubernetesPercent amortizedNetCost: .amortizedNetCost.cost amortizedNetCostPercent: .amortizedNetCost.kubernetesPercent invoicedCost: .invoicedCost.cost invoicedCostPercent: .invoicedCost.kubernetesPercent ``` --- # OpenCost ![Opencost icon](/img/guides/icons/Opencost.svg)![Opencost icon](/img/guides/icons/Opencost.svg) Loading version... Port's Opencost integration allows you to model Opencost resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Cost](https://www.opencost.io/docs/integrations/api-examples#allocation-examples)[Cloudcost](https://www.opencost.io/docs/integrations/api-examples#cloudcost-examples) ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Self-hosted2Prerequisites3Install Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Prerequisites ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: cost selector: query: 'true' port: entity: mappings: blueprint: '"openCostResourceAllocation"' identifier: .name title: .name properties: cluster: .properties.cluster namespace: .properties.namespace startDate: .start endDate: .end cpuCoreHours: .cpuCoreHours cpuCost: .cpuCost cpuEfficiency: .cpuEfficiency gpuHours: .gpuHours gpuCost: .gpuCost networkCost: .networkCost loadBalancerCost: .loadBalancerCost pvCost: .pvCost ramBytes: .ramBytes ramCost: .ramCost ramEfficiency: .ramEfficiency sharedCost: .sharedCost externalCost: .externalCost totalCost: .totalCost totalEfficiency: .totalEfficiency - kind: cloudcost selector: query: 'true' cloudcostAggregate: provider port: entity: mappings: blueprint: '"openCostCloudcost"' identifier: .properties.provider + "-" + .window.start + "-" + .window.end title: .properties.provider + "-" + .window.start + "-" + .window.end properties: startDate: .window.start endDate: .window.end listCost: .listCost.cost netCost: .netCost.cost amortizedNetCost: .amortizedNetCost.cost invoicedCost: .invoicedCost.cost amortizedCost: .amortizedCost.cost ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Examples of blueprints and the relevant integration configurations: CostCloudcost Default mappingYAML ``` - kind: cost selector: query: 'true' port: entity: mappings: blueprint: '"openCostResourceAllocation"' identifier: .name title: .name properties: cluster: .properties.cluster namespace: .properties.namespace startDate: .start endDate: .end cpuCoreHours: .cpuCoreHours cpuCost: .cpuCost cpuEfficiency: .cpuEfficiency gpuHours: .gpuHours gpuCost: .gpuCost networkCost: .networkCost loadBalancerCost: .loadBalancerCost pvCost: .pvCost ramBytes: .ramBytes ramCost: .ramCost ramEfficiency: .ramEfficiency sharedCost: .sharedCost externalCost: .externalCost totalCost: .totalCost totalEfficiency: .totalEfficiency ``` Sample payloadJSON ``` { "name": "ingress-nginx", "properties": { "cluster": "cluster-one", "node": "minikube", "container": "controller", "controller": "ingress-nginx-controller", "controllerKind": "deployment", "namespace": "ingress-nginx", "pod": "ingress-nginx-controller-7799c6795f-29n7j", "services": [ "ingress-nginx-controller-admission", "ingress-nginx-controller" ], "labels": { "app_kubernetes_io_component": "controller", "app_kubernetes_io_instance": "ingress-nginx", "app_kubernetes_io_name": "ingress-nginx", "gcp_auth_skip_secret": "true", "kubernetes_io_metadata_name": "ingress-nginx", "pod_template_hash": "7799c6795f" }, "namespaceLabels": { "app_kubernetes_io_instance": "ingress-nginx", "app_kubernetes_io_name": "ingress-nginx", "kubernetes_io_metadata_name": "ingress-nginx" } }, "window": { "start": "2023-10-30T00:00:00Z", "end": "2023-10-31T00:00:00Z" }, "start": "2023-10-30T09:05:00Z", "end": "2023-10-30T11:50:00Z", "minutes": 165, "cpuCores": 0.1, "cpuCoreRequestAverage": 0.1, "cpuCoreUsageAverage": 0, "cpuCoreHours": 0.275, "cpuCost": 0.00869, "cpuCostAdjustment": 0, "cpuEfficiency": 0, "gpuCount": 0, "gpuHours": 0, "gpuCost": 0, "gpuCostAdjustment": 0, "networkTransferBytes": 0, "networkReceiveBytes": 0, "networkCost": 0, "networkCrossZoneCost": 0, "networkCrossRegionCost": 0, "networkInternetCost": 0, "networkCostAdjustment": 0, "loadBalancerCost": 0, "loadBalancerCostAdjustment": 0, "pvBytes": 0, "pvByteHours": 0, "pvCost": 0, "pvs": "None", "pvCostAdjustment": 0, "ramBytes": 94371840, "ramByteRequestAverage": 94371840, "ramByteUsageAverage": 0, "ramByteHours": 259522560, "ramCost": 0.00102, "ramCostAdjustment": 0, "ramEfficiency": 0, "externalCost": 0, "sharedCost": 0, "totalCost": 0.00972, "totalEfficiency": 0, "lbAllocations": "None" } ``` Port entityJSON ``` { "identifier": "ingress-nginx", "title": "ingress-nginx", "blueprint": "openCostResourceAllocation", "team": [], "properties": { "cluster": "cluster-one", "namespace": "ingress-nginx", "startDate": "2023-10-30T09:05:00.000Z", "endDate": "2023-10-30T11:50:00.000Z", "cpuCoreHours": 0.275, "cpuCost": 0.00869, "cpuEfficiency": 0, "gpuHours": 0, "gpuCost": 0, "networkCost": 0, "loadBalancerCost": 0, "pvCost": 0, "ramBytes": 94371840, "ramCost": 0.00102, "ramEfficiency": 0, "sharedCost": 0, "externalCost": 0, "totalCost": 0.00972, "totalEfficiency": 0 }, "relations": {}, "createdAt": "2023-10-15T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-10-30T11:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `window` | `` | Time window for the OpenCost allocation query. Default: `"today"`. | | `aggregate` | `string` | Field by which to aggregate the results. | | `step` | `string` | Duration of a single allocation set (e.g., '30m', '2h', '1d'). Default is window. | | `resolution` | `string` | Duration to use as resolution in Prometheus queries | Default mappingYAML ``` - kind: cloudcost selector: query: 'true' cloudcostAggregate: provider port: entity: mappings: blueprint: '"openCostCloudcost"' identifier: .properties.provider + "-" + .window.start + "-" + .window.end title: .properties.provider + "-" + .window.start + "-" + .window.end properties: startDate: .window.start endDate: .window.end listCost: .listCost.cost netCost: .netCost.cost amortizedNetCost: .amortizedNetCost.cost invoicedCost: .invoicedCost.cost amortizedCost: .amortizedCost.cost ``` Sample payloadJSON ``` { "properties": { "provider": "AWS", "accountID": "123456789012", "invoiceEntityID": "AWS-123456789012", "service": "Amazon Elastic Compute Cloud", "category": "Compute", "region": "us-east-1", "labels": { "environment": "production", "team": "platform" } }, "window": { "start": "2024-03-01T00:00:00Z", "end": "2024-03-31T23:59:59Z" }, "listCost": { "cost": 1250.75, "kubernetesPercent": 0.85 }, "netCost": { "cost": 1100.50, "kubernetesPercent": 0.85 }, "amortizedNetCost": { "cost": 1050.25, "kubernetesPercent": 0.85 }, "invoicedCost": { "cost": 1250.75, "kubernetesPercent": 0.85 }, "amortizedCost": { "cost": 1200.30, "kubernetesPercent": 0.85 }, "items": [ { "resourceId": "i-0abc123def456789", "name": "eks-node-1", "properties": { "instanceType": "m5.xlarge", "operatingSystem": "Linux" }, "tags": { "Name": "eks-node-1", "kubernetes.io/cluster/my-cluster": "owned" }, "cost": 625.37, "start": "2024-03-01T00:00:00Z", "end": "2024-03-31T23:59:59Z" }, { "resourceId": "i-0def456789abc1234", "name": "eks-node-2", "properties": { "instanceType": "m5.xlarge", "operatingSystem": "Linux" }, "tags": { "Name": "eks-node-2", "kubernetes.io/cluster/my-cluster": "owned" }, "cost": 625.38, "start": "2024-03-01T00:00:00Z", "end": "2024-03-31T23:59:59Z" } ] } ``` Port entityJSON ``` { "identifier": "aws_2024_03_01_t_00_00_00_z_2024_03_31_t_23_59_59_z", "title": "AWS-2024-03-01T00:00:00Z-2024-03-31T23:59:59Z", "properties": { "startDate": "2024-03-01T00:00:00Z", "endDate": "2024-03-31T23:59:59Z", "listCost": 1250.75, "netCost": 1100.5, "amortizedNetCost": 1050.25, "invoicedCost": 1250.75, "amortizedCost": 1200.3 }, "relations": {}, "icon": "Opencost" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `window` | `` | Time window for the OpenCost cloud cost query. Default: `"today"`. | | `aggregate` | `string` | Field by which to aggregate the results of cloudcost | | `accumulate` | `string` | Step size of the accumulation. | | `filter` | `string` | Filter results by any category which that can be aggregated by, can support multiple filterable items in the same category in a comma-separated list. | ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # Cloud providers Port's cloud integrations make it easy to keep your software catalog up to date based on information stored in your cloud provider. For more information, check out the documentation for your respective provider: ## [🗃Azure](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/.md) [2 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/.md) ## [🗃Google Cloud](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/.md) [2 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/.md) ## [🗃AWS Legacy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) [4 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) ## [🗃AWS](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/Overview/.md) [3 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/Overview/.md) --- # Installation Loading version... This page guides you through installing the AWS integration. Choose how you want to run it and the integration syncs your AWS resources into your Port catalog, keeping the catalog up to date through periodic resyncs and optional [live events](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/live-events/.md). The integration supports two deployment options: * **Hosted by Port**: Fully managed by Port with zero infrastructure to maintain. You create IAM roles via CloudFormation, and Port runs the integration on its servers, connecting to your AWS accounts using OIDC authentication. * **Self-hosted**: Runs in your own AWS environment (ECS, EC2, EKS, or a one-off Docker run). You manage the deployment and the IAM roles that grant the integration read-only access. ## Setup[​](#setup "Direct link to Setup") Use the wizard below to select your deployment option and follow the steps. For self-hosted installations, you will first choose whether to sync a single account or multiple accounts across your AWS Organization. 1Hosted by Port2Single account3Prerequisites4Deploy CloudFormation stack5Configure integration in Port6Live events (optional) Hosted by Port * ✧Fully managed by Port — zero infrastructure to maintain * ✧Create IAM roles via CloudFormation; Port runs the integration * ✧Fastest way to get started Self-hosted * ✧Runs in your own AWS environment (ECS, EC2, EKS, or Docker) * ✧You manage the deployment and IAM roles * ✧Full control over where and how it runs Single account Querying specific AWS regions You can control which AWS regions the integration queries using `regionPolicy`. See [Querying resources from specific regions](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/.md#querying-resources-from-specific-regions). ## Account discovery[​](#account-discovery "Direct link to Account discovery") Relevant for multi-account self-hosted setups This section explains how the integration discovers accounts when you choose the **multi-account** self-hosted setup. It does not apply to single-account or Hosted by Port installations. Account discovery is driven by **AWS Organizations**. The integration assumes a role in your **management account** to call the Organizations API, then assumes `PortOceanReadRole` in each discovered account to read and sync resources into your Port catalog. The account list is refreshed on every resync cycle and new accounts are picked up automatically, and removed accounts stop being synced. How accounts are discovered depends on whether you provide a **Target OU ID** during installation: ### Sync all accounts (default)[​](#sync-all-accounts-default "Direct link to Sync all accounts (default)") Leave the **Target OU ID** field empty to sync every active member account in your organization. **When to use:** You want full organization-wide visibility with no account filtering. **How it works:** 1. The integration assumes a role in your management account. 2. It calls the AWS Organizations `ListAccounts` API to enumerate all active accounts in your organization. 3. It assumes `PortOceanReadRole` in each account and syncs resources into your Port catalog. ### Sync accounts in a specific OU[​](#sync-accounts-in-a-specific-ou "Direct link to Sync accounts in a specific OU") Set a **Target OU ID** (`ou-xxxx-xxxxxxxx`) during installation to scope discovery to a specific branch of your organization hierarchy. The integration will discover accounts in that OU and any nested sub-OUs. **When to use:** You want to limit syncing to a subset of accounts. For example, only accounts in your `production` OU, or a single business unit's accounts. **How it works:** 1. The integration assumes a role in your management account. 2. It calls `ListAccountsForParent` with your OU ID and recursively walks all nested sub-OUs to build the account list. 3. It assumes `PortOceanReadRole` in each discovered account and syncs resources into your Port catalog. Find your OU ID in the [AWS Organizations console](https://us-east-1.console.aws.amazon.com/organizations/v2/home/accounts) under **Organizational structure** (format: `ou-xxxx-xxxxxxxx`). ## IAM role architecture[​](#iam-role-architecture "Direct link to IAM role architecture") The integration uses read-only IAM roles to access your AWS resources. How those roles are created and trusted depends on your deployment option. Select a tab below to see the role architecture for your setup. * Hosted by Port * Self-hosted AWS uses a strategically designed IAM role that provides comprehensive read-only access to all AWS services. The role is created automatically by the CloudFormation template and uses the AWS managed `ReadOnlyAccess` policy for future-proofing and operational simplicity. Role modifications We recommend keeping the IAM role and its permissions unchanged after deployment. The integration is designed to work with the full `ReadOnlyAccess` policy, which ensures: * **Complete resource discovery** across all AWS services. * **Future compatibility** when Port adds support for new AWS services. * **Reliable operation** without permission-related issues. If you prefer to create the IAM role manually rather than using the CloudFormation template, the following sections describe the setup we implement. This will help you understand the reasoning behind our design choices and adapt them if you decide to configure the role differently. ### Role structure For multi-account setups, the role structure is replicated across all target accounts. #### OIDC identity provider The CloudFormation template creates an OIDC identity provider that connects to Port's EKS cluster: ``` PortIntegrationOIDCProvider: Type: AWS::IAM::OIDCProvider Properties: Url: 'https://oidc.eks.eu-west-1.amazonaws.com/id/56E5F51C07138118A9183ECEAA68FAF4' ClientIdList: - sts.amazonaws.com Tags: - Key: port:integration Value: !Ref IntegrationIdentifier ``` **Key details**: * **EKS OIDC URL**: Points to Port's production EKS cluster in EU-West-1. * **Client ID**: Uses `sts.amazonaws.com` for IRSA authentication. * **Reusable**: Can be shared across multiple integrations in the same account. * **Optional**: Can be disabled if you already have a Port OIDC provider. #### Trust policy The IAM role uses an **OIDC (OpenID Connect) trust policy** with **IRSA (IAM Roles for Service Accounts)** that allows Port's EKS cluster to assume the role: ``` { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::YOUR_ACCOUNT_ID:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/56E5F51C07138118A9183ECEAA68FAF4" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.eu-west-1.amazonaws.com/id/56E5F51C07138118A9183ECEAA68FAF4:aud": "sts.amazonaws.com", "oidc.eks.eu-west-1.amazonaws.com/id/56E5F51C07138118A9183ECEAA68FAF4:sub": "system:serviceaccount:org-YOUR_ORG_ID-port-oidc-sa" } } }] } ``` **Key security features**: * **OIDC authentication**: Role is tied to Port's specific EKS service account. * **Temporary credentials**: All access uses short-lived tokens (typically 1 hour). #### Permissions policy The role uses the **AWS managed `ReadOnlyAccess` policy**, which provides comprehensive read-only access to all AWS services: ``` ManagedPolicyArns: - arn:aws:iam::aws:policy/ReadOnlyAccess ``` **Strategic benefits**: * **Future-proof**: Automatically includes new AWS services without CloudFormation updates. * **Operational simplicity**: No need to redeploy when adding support for new services. * **Comprehensive coverage**: Access to all AWS services with read-only permissions. * **AWS maintained**: AWS manages and updates the policy as needed. * **Read-only security**: Only read permissions, no write/delete/create access. ### Security considerations From a security view point the integration uses a **read-only** role. It can list/describe and read metadata/tags, but cannot create, modify, delete, or change any resource. With self-hosted deployments you manage IAM resources directly; the hosted version manages them for you. ### Role structure In every deployment, the integration uses one read-only role, **PortOceanReadRole**, with the AWS **ReadOnlyAccess** policy. Who can assume that role depends on where you run the integration: an ECS task, an EC2 instance, or a pod using your EKS cluster's OIDC identity. Select a tab below to see the trust policy for that method. In all cases, the integration process gets an identity (task role, instance role, or OIDC-backed service account). That identity is allowed to assume **PortOceanReadRole**. The read-only role has **ReadOnlyAccess** and is used to call AWS APIs. * IAM Role (ECS) * IAM Role (EC2) * IRSA (EKS) The integration runs in an ECS task and assumes the read-only role **PortOceanReadRole** to call AWS APIs. Two roles apply: * **Task role (PortOceanTaskRole)** — Attached to the task; its only permission is `sts:AssumeRole` on PortOceanReadRole. * **Read-only role (PortOceanReadRole)** — Has the **ReadOnlyAccess** managed policy. Only the task role can assume it. **Flow** — ECS injects the task role credentials into the container. The integration uses them to assume PortOceanReadRole, then calls AWS APIs with that role. ![ECS task role assuming PortOceanReadRole](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws-v3/self-hosted/iam-ecs-flow.svg) **Read-only role** trust policy. Only the task role can assume this role. Replace `TASK_ROLE_ARN` with your task role ARN: ``` { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "AWS": "TASK_ROLE_ARN" }, "Action": "sts:AssumeRole" }] } ``` **Key security features**: AWS provides temporary credentials to ECS tasks automatically. The task role has only assume-role permission; the read-only role holds **ReadOnlyAccess**. AWS STS automatically rotates credentials. ECS tasks can assume roles in other AWS accounts when you configure cross-account access. The integration runs on an EC2 instance and assumes the read-only role **PortOceanReadRole** to call AWS APIs. Two roles apply: * **Instance role (PortOceanInstanceRole)** — Attached to the instance; its only permission is `sts:AssumeRole` on PortOceanReadRole. * **Read-only role (PortOceanReadRole)** — Has the **ReadOnlyAccess** managed policy. Only the instance role can assume it. **Flow** — EC2 delivers the instance role credentials to the instance via the instance profile. The integration assumes PortOceanReadRole, then calls AWS APIs with that role. ![EC2 instance role assuming PortOceanReadRole](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws-v3/self-hosted/iam-ec2-flow.svg) **Read-only role** trust policy. Only the instance role can assume this role. Replace `INSTANCE_ROLE_ARN` with your instance role ARN: ``` { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "AWS": "INSTANCE_ROLE_ARN" }, "Action": "sts:AssumeRole" }] } ``` **Key security features**: EC2 delivers temporary credentials to the instance via the instance profile. The instance role has only assume-role permission; the read-only role holds **ReadOnlyAccess**. AWS STS automatically rotates credentials. With IRSA, the read-only role (**PortOceanReadRole**) trusts your EKS cluster's OIDC identity provider. There is no separate "task" or "instance" role; the pod receives a web identity token from the service account, and PortOceanReadRole trusts that token. **Flow** — The pod uses a Kubernetes service account annotated with the IAM role ARN. EKS and the OIDC provider issue a short-lived token. The integration exchanges it for PortOceanReadRole credentials via `AssumeRoleWithWebIdentity`, then calls AWS APIs. ![EKS pod assuming PortOceanReadRole via IRSA](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws-v3/self-hosted/iam-irsa-flow.svg) ``` { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::MEMBER_ACCOUNT_ID:oidc-provider/oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID:aud": "sts.amazonaws.com", "oidc.eks.YOUR_REGION.amazonaws.com/id/YOUR_OIDC_ID:sub": "system:serviceaccount:YOUR_NAMESPACE:YOUR_SERVICE_ACCOUNT" } } }] } ``` **Key security features**: OIDC federation provides secure identity federation without shared secrets. Roles are tied to specific Kubernetes service accounts, and pods receive short-lived tokens for the role. ### Permissions setup In every deployment method, the integration calls AWS APIs using **PortOceanReadRole**. That role has the **AWS managed ReadOnlyAccess policy** attached, which gives read-only access to AWS services: ![PortOceanReadRole with ReadOnlyAccess policy granting read-only AWS permissions](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws-v3/self-hosted/iam-permissions-flow.svg) ``` ManagedPolicyArns: - arn:aws:iam::aws:policy/ReadOnlyAccess ``` **Benefits**: The policy automatically includes new AWS services so you do not need to redeploy when AWS adds services. AWS maintains and updates it. Access is read-only; create, modify, and delete actions are not allowed. ### Security considerations Self-hosted integrations use **read-only** access with carefully configured trust relationships. You can list and describe resources and read metadata and tags; you cannot create, modify, delete, or change any resource. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") Select your deployment option to troubleshoot installation and sync issues. Use **Interactive mode** to be guided through diagnostic checks, or **Quick reference** to scan the full list of issues and fixes. * Hosted by Port * Self-hosted Interactive modeQuick reference ### What are you experiencing? Select the issue that best matches your situation. We'll guide you through diagnosing and fixing it step by step. CloudFormation stack creation failed The CloudFormation stack that creates the IAM role shows CREATE\_FAILED or ROLLBACK\_COMPLETE → No resources appearing in Port The stack completed successfully but no AWS resources are showing up in your Port catalog → Can I use an existing IAM role? You want to reuse a role you already have instead of the CloudFormation-created one → * Single account * Multi-account Interactive modeQuick reference ### What are you experiencing? Select the issue that best matches your situation. We'll guide you through diagnosing and fixing it step by step. CloudFormation stack creation failed The ECS, EC2, or EKS stack shows CREATE\_FAILED or ROLLBACK\_COMPLETE → No resources discovered The integration is running but no AWS resources appear in your Port catalog → Invalid credentials (IAM User method) Logs show authentication failures when using static access keys → Role or authentication errors Logs show "Unable to assume role", AccessDenied, or AssumeRoleWithWebIdentity errors → Integration not connecting to Port or AWS The stack completed but the container cannot reach APIs or exits immediately after starting → Interactive modeQuick reference ### What are you experiencing? Select the issue that best matches your situation. We'll guide you through diagnosing and fixing it step by step. General configuration check Run through a comprehensive checklist to verify all setup steps are correct → Integration not connecting to Port or AWS Stack completes successfully but the container cannot reach APIs or crashes immediately after starting → No resources appearing in Port catalog Integration is running but no AWS resources are syncing to Port → Container keeps crashing or restarting Integration starts but exits unexpectedly or restarts in a loop → Auth or permission errors Seeing AccessDenied, "Unable to assume role", or InvalidClientTokenId errors in logs → Cannot access management account You do not have access to the AWS Organizations management account to deploy StackSets → Unable to assume role errors Logs show "Unable to assume role" or "AccessDenied" when trying to access member accounts → Resync failed: "Not authorized to perform sts:AssumeRoleWithWebIdentity" (IRSA) Port shows a failed resync, the catalog is empty, and the integration logs contain an AssumeRoleWithWebIdentity AccessDenied error → Auth errors for specific AWS regions Authentication works in most regions but fails for specific opt-in regions like ap-east-1 or me-south-1 → Access denied to Organizations API Logs show "AccessDenied" when trying to list or describe AWS Organizations accounts → --- # Live events Live events keep your Port catalog up to date between scheduled resyncs. AWS records API calls as CloudTrail management events, EventBridge matches supported API calls, and forwards them to Port at `{LiveEventsUrl}/integration/webhook`. The integration authenticates each request with the live events API key and updates the matching catalog entity. Live events are deployed with a **separate CloudFormation stack** from the integration IAM stack. They complement full resyncs but do not replace reconciliation. For how live events interact with resyncs, see [Sync interactions](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/sync-interactions/.md). CloudTrail required Ensure CloudTrail is logging **management events** in the accounts and regions where you deploy live events. Without CloudTrail events, EventBridge has nothing to forward. To deploy the live events CloudFormation stack, follow the **Live events (optional)** step in the [installation wizard](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/installation/.md?step=live-events-optional). ## Supported resource kinds[​](#supported-resource-kinds "Direct link to Supported resource kinds") The integration forwards **CloudTrail management events** for these resource kinds: | Resource kind | Live events | | -------------------------------------- | -------------------------- | | `AWS::S3::Bucket` | Create and delete | | `AWS::Lambda::Function` | Create, update, and delete | | `AWS::DynamoDB::Table` | Create, update, and delete | | `AWS::RDS::DBInstance` | Create, update, and delete | | `AWS::RDS::DBCluster` | Create, update, and delete | | `AWS::ECR::Repository` | Create and delete | | `AWS::ECS::Cluster` | Create, update, and delete | | `AWS::ECS::Service` | Create, update, and delete | | `AWS::ECS::TaskDefinition` | Register and deregister | | `AWS::EKS::Cluster` | Create, update, and delete | | `AWS::EC2::Instance` | Launch and terminate | | `AWS::EC2::Volume` | Create, modify, and delete | | `AWS::ElastiCache::Cluster` | Create, modify, and delete | | `AWS::MemoryDB::User` | Create, update, and delete | | `AWS::CodeBuild::Project` | Create, update, and delete | | `AWS::CodeBuild::BuildRun` | Start, stop, and retry | | `AWS::CodeDeploy::Deployment` | Create and stop | | `AWS::CodePipeline::Pipeline` | Create, update, and delete | | `AWS::CodePipeline::PipelineExecution` | Start and stop | | `AWS::SNS::Topic` | Create, update, and delete | | `AWS::SQS::Queue` | Create, update, and delete | | `AWS::SES::EmailIdentity` | Create, update, and delete | | `AWS::SES::ConfigurationSet` | Create, update, and delete | ## Unsupported resource kinds[​](#unsupported-resource-kinds "Direct link to Unsupported resource kinds") The following kinds are ingested during resyncs but **do not** receive live events today: | Resource kind | Reason | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | | `AWS::Account::Info` | Account metadata is synced from account-level APIs, not from regional CloudTrail CRUD events. | | `AWS::Organizations::Account` | Organization account lifecycle events are not mapped to live events yet. | | `AWS::MSK::Cluster` | Same CloudTrail events also map to `AWS::MSK::ServerlessCluster`, so the kind cannot be identified reliably. | | `AWS::MSK::ServerlessCluster` | Same CloudTrail events also map to `AWS::MSK::Cluster`, so the kind cannot be identified reliably. | | `AWS::CodeDeploy::Application` | CloudTrail exposes mutable application names, not stable ARNs. | | `AWS::CodeDeploy::DeploymentGroup` | CloudTrail exposes mutable group names, not stable ARNs. | | `AWS::CodePipeline::Stage` | Derived from pipeline definitions; CloudTrail has no per-stage CRUD events. | | `AWS::CodePipeline::Action` | Derived from pipeline definitions; CloudTrail has no per-action CRUD events. | | `AWS::CodePipeline::ActionExecution` | No CloudTrail API events; lifecycle updates use CodePipeline EventBridge state-change events not supported by live events yet. | ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Events not appearing in Port * Confirm the live events API key in Port matches the `LiveEventsApiKey` CloudFormation parameter. * Verify CloudTrail is enabled and logging management events in the account and region where you changed the resource. * Check that the resource kind is in the [supported list](#supported-resource-kinds). [Unsupported kinds](#unsupported-resource-kinds) update only during resyncs. * For multi-account setups, confirm StackSet operations succeeded in the [StackSets console](https://console.aws.amazon.com/cloudformation/home#/stacksets). * For self-hosted ECS or EC2 setups, confirm `EnableLiveEvents` is `true` on the Ocean stack and that your public HTTPS URL routes to Ocean on port `8000`. * For self-hosted IRSA or IAM User setups, confirm `OCEAN__BASE_URL` and `OCEAN__INTEGRATION__CONFIG__LIVE_EVENTS_API_KEY` are set and ingress routes HTTPS traffic to `/integration/webhook`. ### Stack creation failed * **Insufficient IAM permissions**: Ensure your AWS user can create CloudFormation stacks, IAM roles, EventBridge connections, and StackSets (for multi-account). * **Regions not enabled**: Live events deploy only to enabled AWS regions. Enable the region in [Account settings](https://console.aws.amazon.com/billing/home#/account) or remove it from the `Regions` parameter. * **Multi-account regions missing**: The `Regions` parameter is required for multi-account live events stacks. --- # AWS ![AWS icon](/img/guides/icons/AWS.svg)![AWS icon](/img/guides/icons/dark/AWS.svg) Loading version... Port's AWS integration imports your AWS resources into Port, with flexible deployment options to match your needs. The integration syncs your AWS resources on a schedule and can optionally process live events so your Port catalog stays up to date with your AWS infrastructure. Supported resources ![External link icon](/img/icons/external-link.svg)[AWS::SQS::Queue](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sqs-queue.html)[AWS::S3::Bucket](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-bucket.html)[AWS::EC2::Instance](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-instance.html)[AWS::EC2::Volume](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volume.html)[AWS::ECR::Repository](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-repository.html)[AWS::ECS::Cluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html)[AWS::ECS::Service](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html)[AWS::ECS::TaskDefinition](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html)[AWS::EKS::Cluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html)[AWS::Organizations::Account](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-organizations-account.html)[AWS::RDS::DBInstance](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbinstance.html)[AWS::RDS::DBCluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html)[AWS::Lambda::Function](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html)[AWS::Account::Info](https://docs.aws.amazon.com/accounts/latest/reference/welcome.html)[AWS::MSK::Cluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html)[AWS::MSK::ServerlessCluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-serverlesscluster.html)[AWS::ElastiCache::Cluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-cachecluster.html)[AWS::MemoryDB::User](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-user.html)[AWS::CodeBuild::Project](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codebuild-project.html)[AWS::CodeBuild::BuildRun](https://docs.aws.amazon.com/codebuild/latest/APIReference/API_Build.html)[AWS::CodeDeploy::Application](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codedeploy-application.html)[AWS::CodeDeploy::DeploymentGroup](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codedeploy-deploymentgroup.html)[AWS::CodeDeploy::Deployment](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_DeploymentInfo.html)[AWS::CodeDeploy::DeploymentTarget](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_DeploymentTarget.html)[AWS::CodePipeline::Pipeline](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codepipeline-pipeline.html)[AWS::CodePipeline::Stage](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_StageDeclaration.html)[AWS::CodePipeline::Action](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_ActionDeclaration.html)[AWS::CodePipeline::PipelineExecution](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_PipelineExecution.html)[AWS::CodePipeline::ActionExecution](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_ActionExecutionDetail.html)[AWS::DynamoDB::Table](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html)[AWS::SES::EmailIdentity](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-emailidentity.html)[AWS::SES::ConfigurationSet](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-configurationset.html)[AWS::SNS::Topic](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sns-topic.html) Port's AWS integration is open source. View the source code [here](https://github.com/port-labs/ocean/tree/main/integrations/aws-v3). Custom kinds not supported AWS only syncs the resource kinds listed above. Unlike the [AWS Legacy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) integration, it does not support defining custom kinds for other [Cloud Control API](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html) resources yet. ## Deployment options[​](#deployment-options "Direct link to Deployment options") The integration can be deployed using one of the following methods: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/installation/.md?troubleshoot=hosted\&method=hosted)**: Fully managed by Port with zero maintenance required. Simply create IAM roles via CloudFormation, and Port handles the rest. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/installation/.md?method=self-hosted)**: Deploy the integration in your own infrastructure using ECS, EC2, EKS, or as a one-off Docker run for complete control. For detailed information about the IAM role architecture, see the [IAM role architecture](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/installation/.md#iam-role-architecture) section of the installation page. ## Live events[​](#live-events "Direct link to Live events") You can optionally enable live events to update supported resource kinds in real time between resyncs. Live events use CloudTrail management events forwarded by EventBridge to Port. Deploy live events with a separate CloudFormation stack after the integration is installed. Follow the **Live events (optional)** step in the [installation wizard](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/installation/.md?step=live-events-optional). Not every resource kind supports live events yet. See [supported](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/live-events/.md#supported-resource-kinds) and [unsupported](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/live-events/.md#unsupported-resource-kinds) resource kinds on the live events page. GovCloud and on-premises support The integration supports AWS GovCloud.
When running a self-hosted GovCloud integration, it is recommended to run using the **AssumeRole** mode. If you run in **StaticCredentials** mode, you will need to set the `OCEAN__INTEGRATION__CONFIG__AWS_PARTITION` environment variable to specify your AWS partition (for example, `aws-us-gov` for GovCloud). ## Common use cases[​](#common-use-cases "Direct link to Common use cases") Easily fill your software catalog with data directly from your AWS Organization, for example: * Map all the resources in your AWS Accounts, including **ECS Clusters**, **S3 Buckets**, and **EC2 Instances** with zero maintenance required. * Keep your Port catalog synchronized with your AWS infrastructure through periodic updates and optional live events. * Use relations to create complete, easily digestible views of your AWS infrastructure inside Port. * Enjoy a fully managed experience with no infrastructure to maintain or updates to apply. Multi-account support The integration supports syncing resources across multiple AWS accounts using automatic account discovery via AWS Organizations. See the [account discovery](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/installation/.md#account-discovery) section of the installation page for details. ## Key advantages[​](#key-advantages "Direct link to Key advantages") AWS provides several advantages over the [AWS Legacy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) integration: * **Fully hosted**: No infrastructure to maintain, update, or monitor. * **Simplified installation**: Just deploy CloudFormation templates to create IAM roles. * **Complete data**: Ensures no missing or incomplete resource information. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. Property naming conventions The AWS integration returns all resource properties in **PascalCase** (for example: `Arn`, `BucketName`, `CreationDate`). When writing mappings and JQ expressions, reference properties using PascalCasing as shown in the examples below. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration you get after installing AWS. **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: AWS::Account::Info selector: query: 'true' port: entity: mappings: identifier: .Properties.Id title: .Properties.Name blueprint: '"awsAccount"' - kind: AWS::S3::Bucket selector: query: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Properties.BucketName blueprint: '"s3Bucket"' properties: arn: .Properties.Arn region: .Properties.LocationConstraint creationDate: .Properties.CreationDate tags: .Properties.Tags relations: account: .__ExtraContext.AccountId - kind: AWS::EC2::Instance selector: query: 'true' port: entity: mappings: identifier: .Properties.InstanceId title: .Properties.InstanceId blueprint: '"ec2Instance"' properties: instanceType: .Properties.InstanceType state: .Properties.State.Name publicIpAddress: .Properties.PublicIpAddress privateIpAddress: .Properties.PrivateIpAddress tags: .Properties.Tags arn: >- "arn:aws:ec2:" + .__Region + ":" + .__AccountId + ":instance/" + .Properties.InstanceId relations: account: .__ExtraContext.AccountId - kind: AWS::ECS::Cluster selector: query: 'true' port: entity: mappings: identifier: .Properties.ClusterArn title: .Properties.ClusterName blueprint: '"ecsCluster"' properties: status: .Properties.Status runningTasksCount: .Properties.RunningTasksCount activeServicesCount: .Properties.ActiveServicesCount pendingTasksCount: .Properties.PendingTasksCount registeredContainerInstancesCount: .Properties.RegisteredContainerInstancesCount capacityProviders: .Properties.CapacityProviders clusterArn: .Properties.ClusterArn tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") The following sections show examples of blueprint definitions and mapping configurations for common AWS resources. SQS QueueS3 BucketEC2 InstanceEC2 VolumeECR RepositoryECS ClusterECS ServiceECS Task DefinitionEKS ClusterOrganizations AccountRDS DB InstanceRDS DB ClusterLambda FunctionAWS Account InfoMSK ClusterMSK Serverless ClusterElastiCache ClusterMemoryDB UserCodeBuild ProjectCodeBuild Build RunCodeDeploy ApplicationCodeDeploy Deployment GroupCodeDeploy DeploymentCodeDeploy Deployment TargetCodePipeline PipelineCodePipeline StageCodePipeline ActionCodePipeline Pipeline ExecutionCodePipeline Action ExecutionDynamoDB TableSES Email IdentitySES Configuration Set BlueprintJSON Create in Port ``` { "identifier": "sqsQueue", "title": "SQS Queue", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN" }, "region": { "type": "string", "title": "Region" }, "queueUrl": { "type": "string", "title": "Queue URL" }, "approximateNumberOfMessages": { "type": "number", "title": "Approx Number of Messages" }, "visibilityTimeout": { "type": "number", "title": "Visibility Timeout (seconds)" }, "messageRetentionPeriod": { "type": "number", "title": "Message Retention Period" }, "sqsManagedSseEnabled": { "type": "boolean", "title": "SQS Managed SSE Enabled" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::SQS::Queue selector: query: 'true' port: entity: mappings: identifier: .Properties.QueueArn title: .Properties.QueueUrl | split("/") | .[-1] blueprint: '"sqsQueue"' properties: arn: .Properties.QueueArn region: .__ExtraContext.Region queueUrl: .Properties.QueueUrl approximateNumberOfMessages: .Properties.ApproximateNumberOfMessages visibilityTimeout: .Properties.VisibilityTimeout messageRetentionPeriod: .Properties.MessageRetentionPeriod sqsManagedSseEnabled: .Properties.SqsManagedSseEnabled relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "s3Bucket", "title": "S3 Bucket", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN" }, "creationDate": { "type": "string", "format": "date-time", "title": "Creation Date" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::S3::Bucket selector: query: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Properties.BucketName blueprint: '"s3Bucket"' properties: arn: .Properties.Arn creationDate: .Properties.CreationDate tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "ec2Instance", "title": "EC2 Instance", "icon": "AWS", "schema": { "properties": { "instanceArn": { "type": "string", "title": "Instance ARN" }, "instanceId": { "type": "string", "title": "Instance ID" }, "instanceType": { "type": "string", "title": "Instance Type" }, "availabilityZone": { "type": "string", "title": "Availability Zone" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::EC2::Instance selector: query: 'true' port: entity: mappings: identifier: .Properties.InstanceId title: .Properties.InstanceId blueprint: '"ec2Instance"' properties: instanceArn: .Properties.InstanceArn instanceId: .Properties.InstanceId instanceType: .Properties.InstanceType availabilityZone: .Properties.AvailabilityZone tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "ebsVolume", "title": "EBS Volume", "icon": "AWS", "schema": { "properties": { "volumeType": { "type": "string", "title": "Volume Type", "description": "The volume type (gp2, gp3, io1, io2, st1, sc1, standard)" }, "size": { "type": "number", "title": "Size (GiB)", "description": "The size of the volume in GiB" }, "iops": { "type": "number", "title": "IOPS", "description": "The number of I/O operations per second" }, "throughput": { "type": "number", "title": "Throughput (MiB/s)", "description": "The throughput provisioned for the volume in MiB/s" }, "availabilityZone": { "type": "string", "title": "Availability Zone", "description": "The Availability Zone in which the volume resides" }, "state": { "type": "string", "title": "State", "description": "The current state of the volume", "enum": [ "creating", "available", "in-use", "deleting", "deleted", "error" ] }, "creationDate": { "type": "string", "title": "Creation Date", "description": "The date and time the volume was created", "format": "date-time" }, "encrypted": { "type": "boolean", "title": "Encrypted", "description": "Indicates whether the volume is encrypted" }, "autoEnableIO": { "type": "boolean", "title": "Auto Enable IO", "description": "Indicates whether the volume is auto-enabled for I/O operations" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the volume", "items": { "type": "object" } }, "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the volume" } }, "required": [] }, "calculationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::EC2::Volume selector: query: 'true' includeActions: - DescribeVolumeAttributeAction port: entity: mappings: identifier: .Properties.VolumeId title: '(.Properties.Tags // [] | map(select(.Key == "Name")) | first | .Value) // .Properties.VolumeId' blueprint: '"ebsVolume"' properties: volumeType: .Properties.VolumeType size: .Properties.Size iops: .Properties.Iops throughput: .Properties.Throughput availabilityZone: .Properties.AvailabilityZone state: .Properties.State creationDate: .Properties.CreateTime encrypted: .Properties.Encrypted autoEnableIO: .Properties.AutoEnableIO tags: .Properties.Tags arn: '"arn:aws:ec2:" + .__ExtraContext.Region + ":" + .__ExtraContext.AccountId + ":volume/" + .Properties.VolumeId' relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "ecrRepository", "title": "ECR Repository", "icon": "AWS", "schema": { "properties": { "repositoryUri": { "type": "string", "title": "Repository URI" }, "registryId": { "type": "string", "title": "Registry ID" }, "createdAt": { "type": "string", "title": "Created At" }, "imageTagMutability": { "type": "string", "title": "Image Tag Mutability" }, "imageScanningConfiguration": { "type": "object", "title": "Image Scanning Configuration" }, "encryptionConfiguration": { "type": "object", "title": "Encryption Configuration" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::ECR::Repository selector: query: 'true' port: entity: mappings: identifier: .Properties.RepositoryArn title: .Properties.RepositoryName blueprint: '"ecrRepository"' properties: repositoryUri: .Properties.RepositoryUri registryId: .Properties.RegistryId createdAt: .Properties.CreatedAt imageTagMutability: .Properties.ImageTagMutability imageScanningConfiguration: .Properties.ImageScanningConfiguration encryptionConfiguration: .Properties.EncryptionConfiguration tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "ecsCluster", "title": "ECS Cluster", "icon": "AWS", "schema": { "properties": { "status": { "type": "string", "title": "Status" }, "runningTasksCount": { "type": "number", "title": "Running Tasks Count" }, "activeServicesCount": { "type": "number", "title": "Active Services Count" }, "pendingTasksCount": { "type": "number", "title": "Pending Tasks Count" }, "registeredContainerInstancesCount": { "type": "number", "title": "Registered Container Instances Count" }, "capacityProviders": { "type": "array", "title": "Capacity Providers" }, "clusterArn": { "type": "string", "title": "Cluster ARN" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::ECS::Cluster selector: query: 'true' port: entity: mappings: identifier: .Properties.ClusterArn title: .Properties.ClusterName blueprint: '"ecsCluster"' properties: status: .Properties.Status runningTasksCount: .Properties.RunningTasksCount activeServicesCount: .Properties.ActiveServicesCount pendingTasksCount: .Properties.PendingTasksCount registeredContainerInstancesCount: .Properties.RegisteredContainerInstancesCount capacityProviders: .Properties.CapacityProviders clusterArn: .Properties.ClusterArn tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "ecsService", "title": "ECS Service", "icon": "AWS", "schema": { "properties": { "serviceName": { "type": "string", "title": "Service Name", "description": "The name of the ECS service" }, "serviceArn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the service" }, "clusterArn": { "type": "string", "title": "Cluster ARN", "description": "The ARN of the cluster that hosts the service" }, "taskDefinition": { "type": "string", "title": "Task Definition", "description": "The ARN of the task definition associated with the service" }, "desiredCount": { "type": "number", "title": "Desired Count", "description": "The desired number of tasks for the service" }, "runningCount": { "type": "number", "title": "Running Count", "description": "The number of tasks that are currently running" }, "pendingCount": { "type": "number", "title": "Pending Count", "description": "The number of tasks in the PENDING state" }, "launchType": { "type": "string", "title": "Launch Type", "description": "The launch type of the service", "enum": [ "EC2", "FARGATE", "EXTERNAL" ] }, "schedulingStrategy": { "type": "string", "title": "Scheduling Strategy", "description": "The scheduling strategy to use for the service", "enum": [ "REPLICA", "DAEMON" ] }, "status": { "type": "string", "title": "Status", "description": "The current status of the service", "enum": [ "ACTIVE", "DRAINING", "INACTIVE" ] }, "deploymentConfiguration": { "type": "object", "title": "Deployment Configuration", "description": "The deployment configuration for the service" }, "deploymentController": { "type": "object", "title": "Deployment Controller", "description": "The deployment controller type for the service" }, "deployments": { "type": "array", "title": "Deployments", "description": "The current deployments of the service", "items": { "type": "object" } }, "enableECSManagedTags": { "type": "boolean", "title": "ECS Managed Tags", "description": "Whether to enable ECS managed tags" }, "enableExecuteCommand": { "type": "boolean", "title": "Execute Command", "description": "Whether to enable ECS Exec for the service" }, "events": { "type": "array", "title": "Events", "description": "The events associated with the service", "items": { "type": "object" } }, "healthCheckGracePeriodSeconds": { "type": "number", "title": "Health Check Grace Period", "description": "The grace period for health checks in seconds" }, "loadBalancers": { "type": "array", "title": "Load Balancers", "description": "The load balancers associated with the service", "items": { "type": "object" } }, "networkConfiguration": { "type": "object", "title": "Network Configuration", "description": "The network configuration for the service" }, "placementConstraints": { "type": "array", "title": "Placement Constraints", "description": "The placement constraints for the service", "items": { "type": "object" } }, "placementStrategy": { "type": "array", "title": "Placement Strategy", "description": "The placement strategy for the service", "items": { "type": "object" } }, "platformFamily": { "type": "string", "title": "Platform Family", "description": "The platform family for the service" }, "platformVersion": { "type": "string", "title": "Platform Version", "description": "The platform version for the service" }, "propagateTags": { "type": "string", "title": "Propagate Tags", "description": "Whether to propagate tags from the service or task definition", "enum": [ "SERVICE", "TASK_DEFINITION" ] }, "roleArn": { "type": "string", "title": "Role ARN", "description": "The ARN of the IAM role associated with the service" }, "serviceRegistries": { "type": "array", "title": "Service Registries", "description": "The service registries associated with the service", "items": { "type": "object" } }, "tags": { "type": "array", "title": "Tags", "description": "The tags associated with the service", "items": { "type": "object" } }, "taskSets": { "type": "array", "title": "Task Sets", "description": "The task sets associated with the service", "items": { "type": "object" } }, "capacityProviderStrategy": { "type": "array", "title": "Capacity Provider Strategy", "description": "The capacity provider strategy for the service", "items": { "type": "object" } }, "availabilityZoneRebalancing": { "type": "string", "title": "Availability Zone Rebalancing", "description": "Whether availability zone rebalancing is enabled", "enum": [ "ENABLED", "DISABLED" ] }, "createdAt": { "type": "string", "title": "Created At", "description": "The timestamp when the service was created", "format": "date-time" }, "createdBy": { "type": "string", "title": "Created By", "description": "The ARN of the entity that created the service" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false }, "cluster": { "title": "Cluster", "target": "ecsCluster", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::ECS::Service selector: query: 'true' port: entity: mappings: identifier: .Properties.ServiceArn title: .Properties.ServiceName blueprint: '"ecsService"' properties: serviceName: .Properties.ServiceName serviceArn: .Properties.ServiceArn clusterArn: .Properties.ClusterArn taskDefinition: .Properties.TaskDefinition desiredCount: .Properties.DesiredCount runningCount: .Properties.RunningCount pendingCount: .Properties.PendingCount launchType: .Properties.LaunchType schedulingStrategy: .Properties.SchedulingStrategy status: .Properties.Status deploymentConfiguration: .Properties.DeploymentConfiguration deploymentController: .Properties.DeploymentController deployments: .Properties.Deployments enableECSManagedTags: .Properties.EnableECSManagedTags enableExecuteCommand: .Properties.EnableExecuteCommand events: .Properties.Events healthCheckGracePeriodSeconds: .Properties.HealthCheckGracePeriodSeconds loadBalancers: .Properties.LoadBalancers networkConfiguration: .Properties.NetworkConfiguration placementConstraints: .Properties.PlacementConstraints placementStrategy: .Properties.PlacementStrategy platformFamily: .Properties.PlatformFamily platformVersion: .Properties.PlatformVersion propagateTags: .Properties.PropagateTags roleArn: .Properties.RoleArn serviceRegistries: .Properties.ServiceRegistries tags: .Properties.Tags taskSets: .Properties.TaskSets capacityProviderStrategy: .Properties.CapacityProviderStrategy availabilityZoneRebalancing: .Properties.AvailabilityZoneRebalancing createdAt: .Properties.CreatedAt createdBy: .Properties.CreatedBy relations: account: .__ExtraContext.AccountId cluster: .Properties.ClusterArn ``` BlueprintJSON Create in Port ``` { "identifier": "ecsTaskDefinition", "title": "ECS Task Definition", "icon": "AWS", "schema": { "properties": { "taskDefinitionArn": { "type": "string", "title": "Task Definition ARN", "description": "The full ARN of the task definition" }, "family": { "type": "string", "title": "Family", "description": "The family name of the task definition" }, "revision": { "type": "number", "title": "Revision", "description": "The revision number of the task definition" }, "status": { "type": "string", "title": "Status", "description": "The status of the task definition", "enum": [ "ACTIVE", "INACTIVE", "DELETE_IN_PROGRESS" ] }, "containerDefinitions": { "type": "array", "title": "Container Definitions", "description": "The container definitions that describe the containers in the task", "items": { "type": "object" } }, "cpu": { "type": "string", "title": "CPU", "description": "The number of CPU units used by the task (e.g. 256, 512, 1024)" }, "memory": { "type": "string", "title": "Memory", "description": "The amount of memory (in MiB) used by the task (e.g. 512, 1024, 2048)" }, "networkMode": { "type": "string", "title": "Network Mode", "description": "The Docker networking mode to use for the containers", "enum": [ "bridge", "host", "awsvpc", "none" ] }, "requiresCompatibilities": { "type": "array", "title": "Requires Compatibilities", "description": "The task launch types the task definition was validated against", "items": { "type": "string" } }, "taskRoleArn": { "type": "string", "title": "Task Role ARN", "description": "The ARN of the IAM role that grants containers permission to call AWS APIs" }, "executionRoleArn": { "type": "string", "title": "Execution Role ARN", "description": "The ARN of the task execution IAM role" }, "volumes": { "type": "array", "title": "Volumes", "description": "The list of data volume definitions for the task", "items": { "type": "object" } }, "placementConstraints": { "type": "array", "title": "Placement Constraints", "description": "The placement constraint objects to use for tasks", "items": { "type": "object" } }, "tags": { "type": "array", "title": "Tags", "description": "The tags associated with the task definition", "items": { "type": "object" } }, "registeredAt": { "type": "string", "title": "Registered At", "format": "date-time", "description": "The timestamp when the task definition was registered" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::ECS::TaskDefinition selector: query: 'true' port: entity: mappings: identifier: .Properties.TaskDefinitionArn title: .Properties.Family + ":" + (.Properties.Revision | tostring) blueprint: '"ecsTaskDefinition"' properties: taskDefinitionArn: .Properties.TaskDefinitionArn family: .Properties.Family revision: .Properties.Revision status: .Properties.Status containerDefinitions: .Properties.ContainerDefinitions cpu: .Properties.Cpu memory: .Properties.Memory networkMode: .Properties.NetworkMode requiresCompatibilities: .Properties.RequiresCompatibilities taskRoleArn: .Properties.TaskRoleArn executionRoleArn: .Properties.ExecutionRoleArn volumes: .Properties.Volumes placementConstraints: .Properties.PlacementConstraints tags: .Properties.Tags registeredAt: .Properties.RegisteredAt relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "eksCluster", "title": "EKS Cluster", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN" }, "name": { "type": "string", "title": "Name" }, "version": { "type": "string", "title": "Kubernetes Version" }, "status": { "type": "string", "title": "Status" }, "endpoint": { "type": "string", "title": "Endpoint" }, "roleArn": { "type": "string", "title": "Role ARN" }, "tags": { "type": "object", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::EKS::Cluster selector: query: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Properties.Name blueprint: '"eksCluster"' properties: arn: .Properties.Arn name: .Properties.Name version: .Properties.Version status: .Properties.Status endpoint: .Properties.Endpoint roleArn: .Properties.RoleArn tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "awsOrganizationAccount", "title": "Organizations Account", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN" }, "email": { "type": "string", "title": "Email" }, "status": { "type": "string", "title": "Status" }, "joinedTimestamp": { "type": "string", "title": "Joined Timestamp" }, "joinedMethod": { "type": "string", "title": "Joined Method" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: AWS::Organizations::Account selector: query: 'true' port: entity: mappings: identifier: .Properties.Id title: .Properties.AccountName blueprint: '"awsOrganizationAccount"' properties: arn: .Properties.Arn email: .Properties.Email status: .Properties.Status joinedTimestamp: .Properties.JoinedTimestamp joinedMethod: .Properties.JoinedMethod ``` BlueprintJSON Create in Port ``` { "identifier": "rdsDbInstance", "title": "RDS DB Instance", "icon": "AWS", "schema": { "properties": { "dbInstanceIdentifier": { "type": "string", "title": "DB Instance Identifier" }, "dbInstanceArn": { "type": "string", "title": "ARN" }, "engine": { "type": "string", "title": "Engine" }, "dbInstanceClass": { "type": "string", "title": "Instance Class" }, "dbInstanceStatus": { "type": "string", "title": "Status" }, "multiAZ": { "type": "boolean", "title": "Multi-AZ" }, "storageEncrypted": { "type": "boolean", "title": "Storage Encrypted" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::RDS::DBInstance selector: query: 'true' port: entity: mappings: identifier: .Properties.DBInstanceArn title: .Properties.DBInstanceIdentifier blueprint: '"rdsDbInstance"' properties: dbInstanceIdentifier: .Properties.DBInstanceIdentifier dbInstanceArn: .Properties.DBInstanceArn engine: .Properties.Engine dbInstanceClass: .Properties.DBInstanceClass dbInstanceStatus: .Properties.DBInstanceStatus multiAZ: .Properties.MultiAZ storageEncrypted: .Properties.StorageEncrypted relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "rdsDbCluster", "title": "RDS DB Cluster", "icon": "AWS", "schema": { "properties": { "activityStreamStatus": { "type": "string", "title": "Activity Stream Status", "description": "The status of the database activity stream" }, "associatedRoles": { "type": "array", "title": "Associated Roles", "description": "The IAM roles associated with the DB cluster", "items": { "type": "object" } }, "autoMinorVersionUpgrade": { "type": "boolean", "title": "Auto Minor Version Upgrade", "description": "Whether minor version upgrades are applied automatically" }, "availabilityZones": { "type": "array", "title": "Availability Zones", "description": "The Availability Zones where the DB cluster is deployed", "items": { "type": "string" } }, "backupRetentionPeriod": { "type": "number", "title": "Backup Retention Period", "description": "The number of days to retain automated backups" }, "clusterCreateTime": { "type": "string", "title": "Cluster Create Time", "description": "The time when the DB cluster was created" }, "copyTagsToSnapshot": { "type": "boolean", "title": "Copy Tags To Snapshot", "description": "Whether to copy tags to snapshots" }, "crossAccountClone": { "type": "boolean", "title": "Cross Account Clone", "description": "Whether cross-account cloning is enabled" }, "databaseName": { "type": "string", "title": "Database Name", "description": "The name of the initial database" }, "dbClusterArn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the DB cluster" }, "dbClusterIdentifier": { "type": "string", "title": "DB Cluster Identifier", "description": "The unique identifier of the RDS DB cluster" }, "dbClusterMembers": { "type": "array", "title": "DB Cluster Members", "description": "The DB instances that belong to the cluster", "items": { "type": "object" } }, "dbClusterResourceId": { "type": "string", "title": "DB Cluster Resource ID", "description": "The unique resource identifier for the DB cluster" }, "deletionProtection": { "type": "boolean", "title": "Deletion Protection", "description": "Whether deletion protection is enabled" }, "earliestRestorableTime": { "type": "string", "title": "Earliest Restorable Time", "description": "The earliest time to which the DB cluster can be restored" }, "endpoint": { "type": "string", "title": "Writer Endpoint", "description": "The writer endpoint for the DB cluster" }, "engine": { "type": "string", "title": "Engine", "description": "The database engine type (e.g., aurora-mysql, aurora-postgresql)" }, "engineMode": { "type": "string", "title": "Engine Mode", "description": "The engine mode of the DB cluster (e.g., provisioned, serverless)" }, "engineVersion": { "type": "string", "title": "Engine Version", "description": "The version of the database engine" }, "globalWriteForwardingStatus": { "type": "string", "title": "Global Write Forwarding Status", "description": "The status of write forwarding for a secondary cluster in a global database" }, "httpEndpointEnabled": { "type": "boolean", "title": "HTTP Endpoint Enabled", "description": "Whether the HTTP endpoint (Data API) is enabled for Aurora Serverless" }, "iamDatabaseAuthenticationEnabled": { "type": "boolean", "title": "IAM Database Authentication Enabled", "description": "Whether IAM database authentication is enabled" }, "kmsKeyId": { "type": "string", "title": "KMS Key ID", "description": "The KMS key identifier for encryption" }, "latestRestorableTime": { "type": "string", "title": "Latest Restorable Time", "description": "The latest time to which the DB cluster can be restored" }, "masterUsername": { "type": "string", "title": "Master Username", "description": "The master username for the DB cluster" }, "multiAZ": { "type": "boolean", "title": "Multi-AZ", "description": "Whether the DB cluster is deployed across multiple Availability Zones" }, "networkType": { "type": "string", "title": "Network Type", "description": "The network type for the DB cluster" }, "port": { "type": "number", "title": "Port", "description": "The port number for the DB cluster" }, "preferredBackupWindow": { "type": "string", "title": "Preferred Backup Window", "description": "The preferred backup window" }, "preferredMaintenanceWindow": { "type": "string", "title": "Preferred Maintenance Window", "description": "The preferred maintenance window" }, "readerEndpoint": { "type": "string", "title": "Reader Endpoint", "description": "The reader endpoint for load-balancing read connections across cluster members" }, "status": { "type": "string", "title": "Status", "description": "The current status of the DB cluster", "enum": [ "available", "backing-up", "creating", "deleting", "failing-over", "inaccessible-encryption-credentials", "maintenance", "migrating", "modifying", "promoting", "renaming", "resetting-master-credentials", "scaling", "starting", "stopped", "stopping", "upgrading" ] }, "storageEncrypted": { "type": "boolean", "title": "Storage Encrypted", "description": "Whether the DB cluster storage is encrypted" }, "storageType": { "type": "string", "title": "Storage Type", "description": "The storage type of the DB cluster" }, "tags": { "type": "array", "title": "Tags", "description": "The tags associated with the DB cluster", "items": { "type": "object", "properties": { "Key": { "type": "string" }, "Value": { "type": "string" } } } }, "vpcSecurityGroups": { "type": "array", "title": "VPC Security Groups", "description": "The VPC security groups associated with the DB cluster", "items": { "type": "object" } }, "dbSubnetGroup": { "type": "string", "title": "DB Subnet Group", "description": "The subnet group associated with the DB cluster" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::RDS::DBCluster selector: query: 'true' port: entity: mappings: identifier: .Properties.DBClusterArn title: .Properties.DBClusterIdentifier blueprint: '"rdsDbCluster"' properties: activityStreamStatus: .Properties.ActivityStreamStatus associatedRoles: .Properties.AssociatedRoles autoMinorVersionUpgrade: .Properties.AutoMinorVersionUpgrade availabilityZones: .Properties.AvailabilityZones backupRetentionPeriod: .Properties.BackupRetentionPeriod clusterCreateTime: .Properties.ClusterCreateTime copyTagsToSnapshot: .Properties.CopyTagsToSnapshot crossAccountClone: .Properties.CrossAccountClone databaseName: .Properties.DatabaseName dbClusterArn: .Properties.DBClusterArn dbClusterIdentifier: .Properties.DBClusterIdentifier dbClusterMembers: .Properties.DBClusterMembers dbClusterResourceId: .Properties.DbClusterResourceId deletionProtection: .Properties.DeletionProtection earliestRestorableTime: .Properties.EarliestRestorableTime endpoint: .Properties.Endpoint engine: .Properties.Engine engineMode: .Properties.EngineMode engineVersion: .Properties.EngineVersion globalWriteForwardingStatus: .Properties.GlobalWriteForwardingStatus httpEndpointEnabled: .Properties.HttpEndpointEnabled iamDatabaseAuthenticationEnabled: .Properties.IAMDatabaseAuthenticationEnabled kmsKeyId: .Properties.KmsKeyId latestRestorableTime: .Properties.LatestRestorableTime masterUsername: .Properties.MasterUsername multiAZ: .Properties.MultiAZ networkType: .Properties.NetworkType port: .Properties.Port preferredBackupWindow: .Properties.PreferredBackupWindow preferredMaintenanceWindow: .Properties.PreferredMaintenanceWindow readerEndpoint: .Properties.ReaderEndpoint status: .Properties.Status storageEncrypted: .Properties.StorageEncrypted storageType: .Properties.StorageType tags: .Properties.Tags vpcSecurityGroups: .Properties.VpcSecurityGroups dbSubnetGroup: .Properties.DBSubnetGroup relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "lambdaFunction", "title": "Lambda Function", "icon": "AWS", "schema": { "properties": { "functionName": { "type": "string", "title": "Function Name" }, "functionArn": { "type": "string", "title": "Function ARN" }, "runtime": { "type": "string", "title": "Runtime" }, "handler": { "type": "string", "title": "Handler" }, "memorySize": { "type": "number", "title": "Memory Size" }, "timeout": { "type": "number", "title": "Timeout" }, "state": { "type": "string", "title": "State" }, "lastModified": { "type": "string", "title": "Last Modified" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::Lambda::Function selector: query: 'true' port: entity: mappings: identifier: .Properties.FunctionArn title: .Properties.FunctionName blueprint: '"lambdaFunction"' properties: functionName: .Properties.FunctionName functionArn: .Properties.FunctionArn runtime: .Properties.Runtime handler: .Properties.Handler memorySize: .Properties.MemorySize timeout: .Properties.Timeout state: .Properties.State lastModified: .Properties.LastModified relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "awsAccount", "title": "AWS Account", "icon": "AWS", "schema": { "properties": {}, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: AWS::Account::Info selector: query: 'true' port: entity: mappings: identifier: .Properties.Id title: .Properties.Name blueprint: '"awsAccount"' ``` BlueprintJSON Create in Port ``` { "identifier": "mskCluster", "title": "MSK Cluster", "icon": "AWS", "schema": { "properties": { "clusterArn": { "type": "string", "title": "ARN" }, "state": { "type": "string", "title": "State" }, "kafkaVersion": { "type": "string", "title": "Kafka Version" }, "numberOfBrokerNodes": { "type": "number", "title": "Number of Broker Nodes" }, "instanceType": { "type": "string", "title": "Instance Type" }, "enhancedMonitoring": { "type": "string", "title": "Enhanced Monitoring" }, "clientAuthentication": { "type": "object", "title": "Client Authentication" }, "encryptionInTransit": { "type": "string", "title": "Encryption In Transit" }, "creationTime": { "type": "string", "title": "Creation Time", "format": "date-time" }, "tags": { "type": "object", "title": "Tags" } }, "required": [] }, "calculationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::MSK::Cluster selector: query: 'true' port: entity: mappings: identifier: .Properties.ClusterArn title: .Properties.ClusterName blueprint: '"mskCluster"' properties: clusterArn: .Properties.ClusterArn state: .Properties.State kafkaVersion: .Properties.CurrentBrokerSoftwareInfo.KafkaVersion numberOfBrokerNodes: .Properties.NumberOfBrokerNodes instanceType: .Properties.BrokerNodeGroupInfo.InstanceType enhancedMonitoring: .Properties.EnhancedMonitoring clientAuthentication: .Properties.ClientAuthentication encryptionInTransit: .Properties.EncryptionInfo.EncryptionInTransit.ClientBroker creationTime: .Properties.CreationTime tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "mskServerlessCluster", "title": "MSK Serverless Cluster", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the MSK serverless cluster" }, "clusterName": { "type": "string", "title": "Cluster Name", "description": "The name of the MSK serverless cluster" }, "state": { "type": "string", "title": "State", "description": "The current state of the cluster", "enum": [ "ACTIVE", "CREATING", "DELETING", "FAILED", "HEALING", "MAINTENANCE", "REBOOTING_BROKER", "UPDATING" ] }, "creationTime": { "type": "string", "title": "Creation Time", "description": "The date and time when the cluster was created", "format": "date-time" }, "currentVersion": { "type": "string", "title": "Current Version", "description": "The current version of the MSK cluster" }, "vpcConfigs": { "type": "array", "title": "VPC Configs", "description": "The VPC configuration for the serverless cluster", "items": { "type": "object" } }, "clientAuthentication": { "type": "object", "title": "Client Authentication", "description": "The client authentication configuration for the cluster" }, "tags": { "type": "object", "title": "Tags", "description": "Tags associated with the cluster" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::MSK::ServerlessCluster selector: query: 'true' port: entity: mappings: identifier: .Properties.ClusterArn title: .Properties.ClusterName blueprint: '"mskServerlessCluster"' properties: arn: .Properties.ClusterArn clusterName: .Properties.ClusterName state: .Properties.State creationTime: .Properties.CreationTime currentVersion: .Properties.CurrentVersion vpcConfigs: .Properties.Serverless.VpcConfigs clientAuthentication: .Properties.Serverless.ClientAuthentication tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "elasticacheCluster", "title": "ElastiCache Cluster", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN" }, "cacheClusterId": { "type": "string", "title": "Cluster ID" }, "cacheClusterStatus": { "type": "string", "title": "Status" }, "cacheNodeType": { "type": "string", "title": "Node Type" }, "engine": { "type": "string", "title": "Engine" }, "engineVersion": { "type": "string", "title": "Engine Version" }, "numCacheNodes": { "type": "number", "title": "Number of Nodes" }, "preferredAvailabilityZone": { "type": "string", "title": "Availability Zone" }, "cacheClusterCreateTime": { "type": "string", "title": "Created At", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::ElastiCache::CacheCluster selector: query: 'true' port: entity: mappings: identifier: .Properties.ARN title: .Properties.CacheClusterId blueprint: '"elasticacheCluster"' properties: arn: .Properties.ARN cacheClusterId: .Properties.CacheClusterId cacheClusterStatus: .Properties.CacheClusterStatus cacheNodeType: .Properties.CacheNodeType engine: .Properties.Engine engineVersion: .Properties.EngineVersion numCacheNodes: .Properties.NumCacheNodes preferredAvailabilityZone: .Properties.PreferredAvailabilityZone cacheClusterCreateTime: .Properties.CacheClusterCreateTime relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "memoryDbUser", "title": "MemoryDB User", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the MemoryDB user" }, "status": { "type": "string", "title": "Status", "description": "The current status of the user (active, modifying, or deleting)" }, "accessString": { "type": "string", "title": "Access String", "description": "Access permissions string used for this user account" }, "aclNames": { "type": "array", "title": "ACL Names", "description": "The names of the Access Control Lists (ACLs) the user belongs to", "items": { "type": "string" } }, "minimumEngineVersion": { "type": "string", "title": "Minimum Engine Version", "description": "The minimum engine version supported for the user" }, "authentication": { "type": "object", "title": "Authentication", "description": "The authentication mode for the user" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the MemoryDB user", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::MemoryDB::User selector: query: 'true' port: entity: mappings: identifier: .Properties.ARN title: .Properties.Name blueprint: '"memoryDbUser"' properties: arn: .Properties.ARN status: .Properties.Status accessString: .Properties.AccessString aclNames: .Properties.ACLNames minimumEngineVersion: .Properties.MinimumEngineVersion authentication: .Properties.Authentication tags: .Properties.TagList relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codeBuildProject", "title": "CodeBuild Project", "icon": "AWS", "schema": { "properties": { "name": { "type": "string", "title": "Project Name", "description": "The name of the CodeBuild project" }, "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the CodeBuild project" }, "description": { "type": "string", "title": "Description", "description": "A description of the CodeBuild project" }, "serviceRole": { "type": "string", "title": "Service Role", "description": "The ARN of the AWS Identity and Access Management (IAM) service role" }, "timeoutInMinutes": { "type": "number", "title": "Timeout (Minutes)", "description": "How long, in minutes, from 5 to 480, for AWS CodeBuild to wait before timing out any related build" }, "queuedTimeoutInMinutes": { "type": "number", "title": "Queued Timeout (Minutes)", "description": "The number of minutes a build is allowed to be queued before timing out" }, "encryptionKey": { "type": "string", "title": "Encryption Key", "description": "The AWS Key Management Service (AWS KMS) customer master key (CMK) to be used for encrypting the build project artifacts" }, "environment": { "type": "object", "title": "Build Environment", "description": "Information about the build environment for the build project" }, "source": { "type": "object", "title": "Source", "description": "Information about the build input source code for the build project" }, "artifacts": { "type": "object", "title": "Artifacts", "description": "Information about the build output artifacts for the build project" }, "created": { "type": "string", "title": "Created", "description": "When the build project was created", "format": "date-time" }, "lastModified": { "type": "string", "title": "Last Modified", "description": "When the build project was last modified", "format": "date-time" }, "projectVisibility": { "type": "string", "title": "Project Visibility", "description": "The visibility setting for the project", "enum": [ "PRIVATE", "PUBLIC_READ" ] }, "concurrentBuildLimit": { "type": "number", "title": "Concurrent Build Limit", "description": "The maximum number of concurrent builds that are allowed for this project" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the CodeBuild project", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodeBuild::Project selector: query: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Properties.Name blueprint: '"codeBuildProject"' properties: name: .Properties.Name arn: .Properties.Arn description: .Properties.Description serviceRole: .Properties.ServiceRole timeoutInMinutes: .Properties.TimeoutInMinutes queuedTimeoutInMinutes: .Properties.QueuedTimeoutInMinutes encryptionKey: .Properties.EncryptionKey environment: .Properties.Environment source: .Properties.Source artifacts: .Properties.Artifacts created: .Properties.Created lastModified: .Properties.LastModified projectVisibility: .Properties.ProjectVisibility concurrentBuildLimit: .Properties.ConcurrentBuildLimit tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codeBuildBuildRun", "title": "CodeBuild Build Run", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the build run" }, "projectName": { "type": "string", "title": "Project Name", "description": "The name of the CodeBuild project" }, "buildNumber": { "type": "number", "title": "Build Number", "description": "The build number" }, "buildStatus": { "type": "string", "title": "Build Status", "description": "The current status of the build", "enum": [ "SUCCEEDED", "FAILED", "FAULT", "TIMED_OUT", "IN_PROGRESS", "STOPPED" ] }, "currentPhase": { "type": "string", "title": "Current Phase", "description": "The current phase of the build" }, "startTime": { "type": "string", "title": "Start Time", "description": "The time the build started", "format": "date-time" }, "endTime": { "type": "string", "title": "End Time", "description": "The time the build ended", "format": "date-time" }, "sourceVersion": { "type": "string", "title": "Source Version", "description": "The version of the source code" }, "buildComplete": { "type": "boolean", "title": "Build Complete", "description": "Whether the build is complete" }, "initiator": { "type": "string", "title": "Initiator", "description": "The entity that initiated the build" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodeBuild::BuildRun selector: query: 'true' port: entity: mappings: identifier: .Properties.Id title: .Properties.ProjectName + " - Build " + (.Properties.BuildNumber | tostring) blueprint: '"codeBuildBuildRun"' properties: arn: .Properties.Arn projectName: .Properties.ProjectName buildNumber: .Properties.BuildNumber buildStatus: .Properties.BuildStatus currentPhase: .Properties.CurrentPhase startTime: .Properties.StartTime endTime: .Properties.EndTime sourceVersion: .Properties.SourceVersion buildComplete: .Properties.BuildComplete initiator: .Properties.Initiator relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codeDeployApplication", "title": "CodeDeploy Application", "icon": "AWS", "schema": { "properties": { "applicationName": { "type": "string", "title": "Application Name" }, "applicationId": { "type": "string", "title": "Application ID" }, "createTime": { "type": "string", "title": "Create Time", "format": "date-time" }, "linkedToGitHub": { "type": "boolean", "title": "Linked To GitHub" }, "gitHubAccountName": { "type": "string", "title": "GitHub Account Name" }, "computePlatform": { "type": "string", "title": "Compute Platform" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the Application", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodeDeploy::Application selector: query: 'true' port: entity: mappings: identifier: .Properties.ApplicationId title: .Properties.ApplicationName blueprint: '"codeDeployApplication"' properties: applicationName: .Properties.ApplicationName applicationId: .Properties.ApplicationId createTime: .Properties.CreateTime linkedToGitHub: .Properties.LinkedToGitHub gitHubAccountName: .Properties.GitHubAccountName computePlatform: .Properties.ComputePlatform tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codedeployDeploymentGroup", "title": "CodeDeploy Deployment Group", "icon": "AWS", "schema": { "properties": { "applicationName": { "type": "string", "title": "Application Name", "description": "The CodeDeploy application name" }, "deploymentGroupId": { "type": "string", "title": "Deployment Group ID", "description": "The deployment group ID" }, "serviceRoleArn": { "type": "string", "title": "Service Role ARN", "description": "The ARN of the service role for the deployment group" }, "computePlatform": { "type": "string", "title": "Compute Platform", "description": "The compute platform for the deployment group", "enum": [ "Server", "Lambda", "ECS" ] }, "deploymentStyle": { "type": "object", "title": "Deployment Style", "description": "Information about the type of deployment" }, "autoScalingGroups": { "type": "array", "title": "Auto Scaling Groups", "description": "A list of associated Auto Scaling groups", "items": { "type": "object" } }, "loadBalancerInfo": { "type": "object", "title": "Load Balancer Info", "description": "Information about the load balancer used in the deployment" }, "outdatedInstancesStrategy": { "type": "string", "title": "Outdated Instances Strategy", "description": "Indicates what happens when new Amazon EC2 instances are launched", "enum": [ "UPDATE", "IGNORE" ] }, "tags": { "type": "array", "title": "Tags", "description": "The metadata tags associated with the deployment group", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodeDeploy::DeploymentGroup selector: query: 'true' port: entity: mappings: identifier: .Properties.DeploymentGroupId title: .Properties.ApplicationName + "/" + .Properties.DeploymentGroupName blueprint: '"codedeployDeploymentGroup"' properties: applicationName: .Properties.ApplicationName deploymentGroupId: .Properties.DeploymentGroupId serviceRoleArn: .Properties.ServiceRoleArn computePlatform: .Properties.ComputePlatform deploymentStyle: .Properties.DeploymentStyle autoScalingGroups: .Properties.AutoScalingGroups loadBalancerInfo: .Properties.LoadBalancerInfo outdatedInstancesStrategy: .Properties.OutdatedInstancesStrategy tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codeDeployDeployment", "title": "CodeDeploy Deployment", "icon": "AWS", "schema": { "properties": { "deploymentId": { "type": "string", "title": "Deployment ID", "description": "The unique ID of the deployment" }, "applicationName": { "type": "string", "title": "Application Name", "description": "The name of the application" }, "deploymentGroupName": { "type": "string", "title": "Deployment Group", "description": "The name of the deployment group" }, "status": { "type": "string", "title": "Status", "description": "The status of the deployment", "enum": [ "Created", "Queued", "InProgress", "Succeeded", "Failed", "Stopped", "Ready" ] }, "creator": { "type": "string", "title": "Creator", "description": "The user who created the deployment" }, "createTime": { "type": "string", "title": "Create Time", "description": "The time the deployment was created", "format": "date-time" }, "startTime": { "type": "string", "title": "Start Time", "description": "The time the deployment started", "format": "date-time" }, "completeTime": { "type": "string", "title": "Complete Time", "description": "The time the deployment completed", "format": "date-time" }, "description": { "type": "string", "title": "Description", "description": "A comment about the deployment" }, "deploymentStyle": { "type": "object", "title": "Deployment Style", "description": "Information about the type of deployment" }, "region": { "type": "string", "title": "Region", "description": "AWS region where the deployment is located" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodeDeploy::Deployment selector: query: 'true' port: entity: mappings: identifier: .Properties.DeploymentId title: .Properties.ApplicationName + "/" + .Properties.DeploymentGroupName + "/" + .Properties.DeploymentId blueprint: '"codeDeployDeployment"' properties: deploymentId: .Properties.DeploymentId applicationName: .Properties.ApplicationName deploymentGroupName: .Properties.DeploymentGroupName status: .Properties.Status creator: .Properties.Creator createTime: .Properties.CreateTime startTime: .Properties.StartTime completeTime: .Properties.CompleteTime description: .Properties.Description deploymentStyle: .Properties.DeploymentStyle region: .__ExtraContext.Region relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codedeployDeploymentTarget", "title": "CodeDeploy Deployment Target", "icon": "AWS", "schema": { "properties": { "deploymentId": { "type": "string", "title": "Deployment ID", "description": "The unique ID of the deployment" }, "deploymentTargetType": { "type": "string", "title": "Deployment Target Type", "description": "The deployment type that is specific to the deployment's compute platform", "enum": [ "InstanceTarget", "LambdaTarget", "ECSTarget", "CloudFormationTarget" ] } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodeDeploy::DeploymentTarget selector: query: 'true' port: entity: mappings: identifier: .Properties.DeploymentId + "/" + .Properties.TargetId title: .Properties.DeploymentId + "/" + .Properties.TargetId blueprint: '"codedeployDeploymentTarget"' properties: deploymentId: .Properties.DeploymentId deploymentTargetType: .Properties.DeploymentTargetType relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codePipeline", "title": "CodePipeline Pipeline", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the pipeline" }, "roleArn": { "type": "string", "title": "Role ARN", "description": "The service role ARN for the pipeline" }, "executionMode": { "type": "string", "title": "Execution Mode", "description": "The execution mode of the pipeline" }, "pipelineType": { "type": "string", "title": "Pipeline Type", "description": "The type of the pipeline" }, "version": { "type": "number", "title": "Version", "description": "The version number of the pipeline" }, "stages": { "type": "array", "title": "Stages", "description": "The stages of the pipeline", "items": { "type": "object" } }, "created": { "type": "string", "title": "Created", "description": "The date when the pipeline was created", "format": "date-time" }, "updated": { "type": "string", "title": "Updated", "description": "The date when the pipeline was last updated", "format": "date-time" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the pipeline", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodePipeline::Pipeline selector: query: 'true' port: entity: mappings: identifier: .Properties.PipelineArn title: .Properties.Name blueprint: '"codePipeline"' properties: arn: .Properties.PipelineArn roleArn: .Properties.RoleArn executionMode: .Properties.ExecutionMode pipelineType: .Properties.PipelineType version: .Properties.Version stages: .Properties.Stages created: .Properties.Created updated: .Properties.Updated tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codePipelineStage", "title": "CodePipeline Stage", "icon": "AWS", "schema": { "properties": { "pipelineName": { "type": "string", "title": "Pipeline Name", "description": "The name of the CodePipeline pipeline this stage belongs to" }, "pipelineArn": { "type": "string", "title": "Pipeline ARN", "description": "The Amazon Resource Name (ARN) of the pipeline" }, "actions": { "type": "array", "title": "Actions", "description": "The list of actions that are part of this stage", "items": { "type": "object" } }, "name": { "type": "string", "title": "Name", "description": "The name of the stage in the pipeline" }, "order": { "type": "number", "title": "Order", "description": "The sequential order of the stage within the pipeline" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodePipeline::Stage selector: query: 'true' port: entity: mappings: identifier: (.Properties.PipelineArn + "/" + .Properties.Name) title: (.Properties.PipelineName + "/" + .Properties.Name) blueprint: '"codePipelineStage"' properties: pipelineName: .Properties.PipelineName pipelineArn: .Properties.PipelineArn name: .Properties.Name order: .Properties.Order actions: .Properties.Actions relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codePipelineAction", "title": "CodePipeline Action", "icon": "AWS", "schema": { "properties": { "name": { "type": "string", "title": "Name", "description": "The name of the action" }, "provider": { "type": "string", "title": "Provider", "description": "The action provider (e.g., S3, CodeBuild, CodeDeploy)" }, "category": { "type": "string", "title": "Category", "description": "The action category (Source, Build, Deploy, etc.)" }, "owner": { "type": "string", "title": "Owner", "description": "The action owner (AWS, ThirdParty, Custom)" }, "version": { "type": "string", "title": "Version", "description": "The action version" }, "runOrder": { "type": "number", "title": "Run Order", "description": "The order in which the action runs" }, "configuration": { "type": "object", "title": "Configuration", "description": "Action configuration parameters" }, "inputArtifacts": { "type": "array", "title": "Input Artifacts", "description": "Input artifacts for the action", "items": { "type": "object" } }, "outputArtifacts": { "type": "array", "title": "Output Artifacts", "description": "Output artifacts produced by the action", "items": { "type": "object" } }, "roleArn": { "type": "string", "title": "Role ARN", "description": "The ARN of the role used for the action" }, "region": { "type": "string", "title": "Region", "description": "The region where the action runs" }, "pipelineName": { "type": "string", "title": "Pipeline Name", "description": "The name of the pipeline containing this action" }, "stageName": { "type": "string", "title": "Stage Name", "description": "The name of the stage containing this action" }, "pipelineArn": { "type": "string", "title": "Pipeline ARN", "description": "The ARN of the pipeline" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodePipeline::Action selector: query: 'true' port: entity: mappings: identifier: .Properties.PipelineArn + "/" + .Properties.StageName + "/" + .Properties.Name title: .Properties.PipelineName + "/" + .Properties.StageName + "/" + .Properties.Name blueprint: '"codePipelineAction"' properties: name: .Properties.Name provider: .Properties.ActionTypeId.Provider category: .Properties.ActionTypeId.Category owner: .Properties.ActionTypeId.Owner version: .Properties.ActionTypeId.Version runOrder: .Properties.RunOrder configuration: .Properties.Configuration inputArtifacts: .Properties.InputArtifacts outputArtifacts: .Properties.OutputArtifacts roleArn: .Properties.RoleArn region: .Properties.Region pipelineName: .Properties.PipelineName stageName: .Properties.StageName pipelineArn: .Properties.PipelineArn relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codePipelinePipelineExecution", "title": "CodePipeline Pipeline Execution", "icon": "AWS", "schema": { "properties": { "pipelineName": { "type": "string", "title": "Pipeline Name", "description": "The name of the pipeline" }, "pipelineVersion": { "type": "number", "title": "Pipeline Version", "description": "The version of the pipeline definition" }, "pipelineExecutionId": { "type": "string", "title": "Execution ID", "description": "The unique identifier for the pipeline execution" }, "status": { "type": "string", "title": "Status", "description": "The status of the pipeline execution", "enum": [ "InProgress", "Stopped", "Stopping", "Succeeded", "Superseded", "Failed" ] }, "statusSummary": { "type": "string", "title": "Status Summary", "description": "A summary of the status of the pipeline execution" }, "executionMode": { "type": "string", "title": "Execution Mode", "description": "The execution mode for this pipeline execution", "enum": [ "QUEUED", "SUPERSEDED", "PARALLEL" ] }, "trigger": { "type": "object", "title": "Trigger", "description": "The trigger that caused the pipeline execution" }, "artifactRevisions": { "type": "array", "title": "Artifact Revisions", "description": "A list of source code revisions for the pipeline execution", "items": { "type": "object" } }, "variables": { "type": "array", "items": { "type": "object" }, "title": "Variables", "description": "Variables defined for the pipeline execution" }, "lastUpdateTime": { "type": "string", "format": "date-time", "title": "Last Update Time", "description": "The time when the pipeline execution was last updated", "dateFormat": "relative" }, "startTime": { "type": "string", "format": "date-time", "title": "StartTime", "description": "The time when the pipeline execution started", "dateFormat": "relative" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodePipeline::PipelineExecution selector: query: 'true' port: entity: mappings: identifier: .Properties.PipelineExecutionId title: .Properties.PipelineName + "/" + .Properties.PipelineExecutionId blueprint: '"codePipelinePipelineExecution"' properties: pipelineName: .Properties.PipelineName pipelineVersion: .Properties.PipelineVersion pipelineExecutionId: .Properties.PipelineExecutionId status: .Properties.Status statusSummary: .Properties.StatusSummary executionMode: .Properties.ExecutionMode trigger: .Properties.Trigger artifactRevisions: .Properties.ArtifactRevisions variables: .Properties.Variables startTime: .Properties.StartTime lastUpdateTime: .Properties.LastUpdateTime relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "codePipelineActionExecution", "title": "CodePipeline Action Execution", "icon": "AWS", "schema": { "properties": { "actionExecutionId": { "type": "string", "title": "Action Execution ID", "description": "The unique ID of the action execution" }, "actionName": { "type": "string", "title": "Action Name", "description": "The name of the action" }, "stageName": { "type": "string", "title": "Stage Name", "description": "The name of the stage containing the action" }, "pipelineExecutionId": { "type": "string", "title": "Pipeline Execution ID", "description": "The pipeline execution ID associated with this action execution" }, "pipelineVersion": { "type": "number", "title": "Pipeline Version", "description": "The version of the pipeline when the action was executed" }, "status": { "type": "string", "title": "Status", "description": "The status of the action execution", "enum": [ "InProgress", "Abandoned", "Succeeded", "Failed" ] }, "startTime": { "type": "string", "title": "Start Time", "description": "The start time of the action execution", "format": "date-time" }, "lastUpdateTime": { "type": "string", "title": "Last Update Time", "description": "The last update time of the action execution", "format": "date-time" }, "input": { "type": "object", "title": "Input", "description": "Input details for the action execution" }, "output": { "type": "object", "title": "Output", "description": "Output details for the action execution" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::CodePipeline::ActionExecution selector: query: 'true' port: entity: mappings: identifier: .Properties.ActionExecutionId title: .Properties.ActionName + "/" + .Properties.ActionExecutionId blueprint: '"codePipelineActionExecution"' properties: actionExecutionId: .Properties.ActionExecutionId actionName: .Properties.ActionName stageName: .Properties.StageName pipelineExecutionId: .Properties.PipelineExecutionId pipelineVersion: .Properties.PipelineVersion status: .Properties.Status startTime: .Properties.StartTime lastUpdateTime: .Properties.LastUpdateTime input: .Properties.Input output: .Properties.Output relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "dynamodbTable", "title": "DynamoDB Table", "description": "This blueprint represents an AWS DynamoDB table in our software catalog", "icon": "AmazonDynamoDB", "schema": { "properties": { "tableArn": { "type": "string", "title": "Table ARN", "description": "The Amazon Resource Name (ARN) of the DynamoDB table" }, "tableStatus": { "type": "string", "title": "Status", "description": "The current status of the table" }, "billingMode": { "type": "string", "title": "Billing mode", "description": "The billing mode of the table (PROVISIONED or PAY_PER_REQUEST)" }, "itemCount": { "type": "number", "title": "Item count", "description": "The number of items in the table" }, "tableSizeBytes": { "type": "number", "title": "Table size (bytes)", "description": "The total size of the table in bytes" }, "creationDateTime": { "type": "string", "title": "Creation date", "description": "The date and time when the table was created", "format": "date-time" }, "deletionProtectionEnabled": { "type": "boolean", "title": "Deletion protection", "description": "Whether deletion protection is enabled for the table" }, "encryptionType": { "type": "string", "title": "Encryption type", "description": "The type of server-side encryption (AES256 or KMS)" }, "region": { "type": "string", "title": "Region", "description": "The AWS region where the table resides" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the table", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` Mapping configurationYAML ``` resources: - kind: AWS::DynamoDB::Table selector: query: 'true' includeActions: - GetTableTagsAction port: entity: mappings: identifier: .Properties.TableArn title: .Properties.TableName blueprint: '"dynamodbTable"' properties: tableArn: .Properties.TableArn tableStatus: .Properties.TableStatus billingMode: .Properties.BillingModeSummary.BillingMode itemCount: .Properties.ItemCount tableSizeBytes: .Properties.TableSizeBytes creationDateTime: .Properties.CreationDateTime deletionProtectionEnabled: .Properties.DeletionProtectionEnabled encryptionType: .Properties.SSEDescription.SSEType region: .__ExtraContext.Region tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "sesEmailIdentity", "title": "SES Email Identity", "icon": "AWS", "schema": { "properties": { "identityType": { "type": "string", "title": "Identity Type", "description": "The type of the email identity (EMAIL_ADDRESS or DOMAIN)" }, "verifiedForSendingStatus": { "type": "boolean", "title": "Verified For Sending Status", "description": "Indicates whether the identity is verified for sending email" }, "dkimEnabled": { "type": "boolean", "title": "DKIM Enabled", "description": "Indicates whether DKIM signing is enabled for the identity" }, "verificationStatus": { "type": "string", "title": "Verification Status", "description": "The verification status of the email identity" }, "configurationSetName": { "type": "string", "title": "Configuration Set Name", "description": "The name of the configuration set associated with the email identity" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the email identity", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::SES::EmailIdentity selector: query: 'true' port: entity: mappings: identifier: .Properties.IdentityName title: .Properties.IdentityName blueprint: '"sesEmailIdentity"' properties: identityType: .Properties.IdentityType verifiedForSendingStatus: .Properties.VerifiedForSendingStatus dkimEnabled: .Properties.DkimAttributes.SigningEnabled verificationStatus: .Properties.VerificationStatus configurationSetName: .Properties.ConfigurationSetName tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "sesConfigurationSet", "title": "SES Configuration Set", "icon": "AWS", "schema": { "properties": { "sendingEnabled": { "type": "boolean", "title": "Sending Enabled", "description": "Indicates whether email sending is enabled for the configuration set" }, "reputationMetricsEnabled": { "type": "boolean", "title": "Reputation Metrics Enabled", "description": "Indicates whether reputation metrics tracking is enabled" }, "tlsPolicy": { "type": "string", "title": "TLS Policy", "description": "The TLS policy that applies to the configuration set (REQUIRE or OPTIONAL)" }, "trackingDomain": { "type": "string", "title": "Tracking Domain", "description": "The domain used to track open and click events" }, "suppressionList": { "type": "array", "title": "Suppression List", "description": "The reasons that email addresses are automatically added to the suppression list", "items": { "type": "string" } }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the configuration set", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::SES::ConfigurationSet selector: query: 'true' port: entity: mappings: identifier: .Properties.ConfigurationSetName title: .Properties.ConfigurationSetName blueprint: '"sesConfigurationSet"' properties: sendingEnabled: .Properties.SendingOptions.SendingEnabled reputationMetricsEnabled: .Properties.ReputationOptions.ReputationMetricsEnabled tlsPolicy: .Properties.DeliveryOptions.TlsPolicy trackingDomain: .Properties.TrackingOptions.CustomRedirectDomain suppressionList: .Properties.SuppressionOptions.SuppressedReasons tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` ## Resource and property reference[​](#resource-and-property-reference "Direct link to Resource and property reference") Refer to the [resource and property reference](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/.md) page for details on available AWS resources, their properties, and mapping examples. --- # AWS resource and property reference Welcome to the reference guide for AWS resource, actions and properties, as supported by AWS CloudFormation and used in Port integrations. ## Supported resource types[​](#supported-resource-types "Direct link to Supported resource types") Explore detailed documentation for each AWS resource and action available for integration. For each resource type, you will find information about supported actions, required permissions, and usage best practices. Resource type identifiers follow a standardized format, allowing you to easily recognize and reference each AWS entity: ``` service-provider::service-name::data-type-name ``` For example, an EC2 instance is identified as: ``` AWS::EC2::Instance ``` Refer to the sidebar or the list below to navigate to individual resource reference pages where you will find actionable integration details, sample configurations, and official AWS documentation links. * [Amazon Simple Queue Service](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-simple-queue-service/.md) * [Amazon Simple Storage Service](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-simple-storage-service/.md) * [Amazon Elastic Compute Cloud](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elastic-compute-cloud/.md) * [Amazon Elastic Container Service](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elastic-container-service/.md) * [Amazon Elastic Container Registry](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elastic-container-registry/.md) * [Amazon Elastic Kubernetes Service](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elastic-kubernetes-service/.md) * [Amazon Relational Database Service](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-relational-database-service/.md) * [AWS Lambda](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-lambda/.md) * [AWS Account](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-account/.md) * [AWS Organizations](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-organizations/.md) * [Amazon Managed Streaming for Apache Kafka (MSK)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-managed-streaming-for-apache-kafka/.md) * [Amazon ElastiCache](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-elasticache/.md) * [Amazon MemoryDB](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-memorydb/.md) * [AWS CodeBuild](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-codebuild/.md) * [AWS CodeDeploy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-codedeploy/.md) * [AWS CodePipeline](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-codepipeline/.md) * [Amazon DynamoDB](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/amazon-dynamodb/.md) More resource types coming soon We're continuously expanding support for additional AWS resource types to ensure comprehensive coverage of your AWS infrastructure. If you need support for a specific AWS resource or action that isn’t yet available, please consider opening a feature request or reach out to us through chat, Slack, or our [Support Portal](http://support.port.io/). Your feedback helps us prioritize what to build next. ## Actions[​](#actions "Direct link to Actions") **Actions** define the set of API operations performed to discover and fetch resources within your AWS environment. These actions are mapped directly to AWS API calls—for example, `DescribeInstances` for EC2 or `ListBuckets` for S3—which determine the resource data that Port can ingest and keep up to date. Ensure required AWS permissions are granted Each action requires specific AWS IAM permissions. Your integration’s AWS credentials must have these permissions; otherwise, the action will not succeed and its data will not be available in Port. ### How Actions Map to AWS Operations[​](#how-actions-map-to-aws-operations "Direct link to How Actions Map to AWS Operations") Each Port-supported resource type comes with a set of actions that align with AWS's official API operations. For instance: * For **Amazon S3 Buckets**, the `ListBucketsAction` leverages the [ListBuckets API](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html), and additional actions like `GetBucketTaggingAction` or `GetBucketEncryptionAction` invoke their respective AWS endpoints. * For **EC2 Instances**, `DescribeInstancesAction` maps to the [DescribeInstances API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html) and brings in detailed instance configuration. These mappings ensure that only the properties available from the selected AWS actions are ingested into your Port catalog. ### Enabling and customizing actions[​](#enabling-and-customizing-actions "Direct link to Enabling and customizing actions") With Port, you have full control over which AWS resource properties are discovered and ingested into your catalog by specifying the actions to use for each resource type. Actions define what data you collect and the AWS permissions required. * **Default Actions**: These are enabled automatically, ensuring basic discovery such as listing all S3 buckets or EC2 instances without additional configuration. * **Optional Actions**: Enable these to collect more detailed properties for your AWS resources. You must explicitly add optional actions in your integration configuration for Port to use them. To collect additional properties from AWS resources, add the optional actions you need to the `includeActions` field of your configuration. For example, to include status details from EC2 instances by enabling the `DescribeInstanceStatusAction`, add the following: ``` - kind: AWS::EC2::Instance selector: query: 'true' includeActions: - DescribeInstanceStatusAction ``` In this case, Port will enrich your catalog by fetching all properties returned by the [DescribeInstanceStatus API](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstanceStatus.html) for every EC2 instance. Maximum actions per resource You can include a **maximum of 3 optional actions per resource kind** (default actions excluded). To use more than 3 optional actions, configure multiple resource kinds in your integration. ### Querying resources from specific regions[​](#querying-resources-from-specific-regions "Direct link to Querying resources from specific regions") You can control which AWS regions Port queries for resources using the `regionPolicy` selector. This allows you to include or exclude specific regions per resource type. * allow: List of regions explicitly permitted for querying * deny: List of regions explicitly excluded from querying #### How `regionPolicy` works[​](#how-regionpolicy-works "Direct link to how-regionpolicy-works") 1. If both lists are empty, all regions are allowed. 2. If a region is listed in `deny`, it is excluded unless explicitly allowed. 3. If a region is listed in `allow`, it is included for querying. 4. If a region is present in both lists, it is excluded. 5. If only `deny` is specified, only the regions in `deny` are excluded. 6. If only `allow` is specified, only the regions in `allow` are included. #### Example configuration[​](#example-configuration "Direct link to Example configuration") ``` resources: - kind: AWS::Lambda::Function selector: query: 'true' regionPolicy: allow: ["us-east-1", "eu-west-1"] deny: ["us-west-2"] port: entity: mappings: identifier: .Properties.Arn title: .Properties.FunctionName blueprint: '"lambda"' properties: region: .__Region description: .Properties.Description arn: .Properties.Arn relations: account: .__ExtraContext.AccountId ``` In this example, resources in `us-east-1` and `eu-west-1` are allowed, while `us-west-2` is denied. Refer to the individual resource documentation pages for a table of actions, their AWS mapping, and necessary IAM permissions. --- # Amazon DynamoDB ## AWS::DynamoDB::Table[​](#awsdynamodbtable "Direct link to AWS::DynamoDB::Table") The following example demonstrates how to ingest your AWS DynamoDB tables to Port. #### DynamoDB Table supported actions[​](#dynamodb-table-supported-actions "Direct link to DynamoDB Table supported actions") The table below summarizes the available actions for ingesting Amazon DynamoDB Table resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------ | | [ListTablesAction](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTables.html) | Discover DynamoDB tables in each region. | Default | `dynamodb:ListTables` | | [GetTableDetailsAction](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeTable.html) | Retrieve detailed table configuration including status, billing mode, size, item count, encryption, and deletion protection. | Default | `dynamodb:DescribeTable` | | [GetTableTagsAction](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_ListTagsOfResource.html) | Retrieve tags associated with each table. Required to populate the `tags` property. | Optional | `dynamodb:ListTagsOfResource` | | [GetTableBackupStatusAction](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DescribeContinuousBackups.html) | Retrieve continuous backup and point-in-time recovery status for each table. | Optional | `dynamodb:DescribeContinuousBackups` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **DynamoDB Table blueprint (click to expand)** Create in Port ``` { "identifier": "dynamodbTable", "title": "DynamoDB Table", "description": "This blueprint represents an AWS DynamoDB table in our software catalog", "icon": "AmazonDynamoDB", "schema": { "properties": { "tableArn": { "type": "string", "title": "Table ARN", "description": "The Amazon Resource Name (ARN) of the DynamoDB table" }, "tableStatus": { "type": "string", "title": "Status", "description": "The current status of the table" }, "billingMode": { "type": "string", "title": "Billing mode", "description": "The billing mode of the table (PROVISIONED or PAY_PER_REQUEST)" }, "itemCount": { "type": "number", "title": "Item count", "description": "The number of items in the table" }, "tableSizeBytes": { "type": "number", "title": "Table size (bytes)", "description": "The total size of the table in bytes" }, "creationDateTime": { "type": "string", "title": "Creation date", "description": "The date and time when the table was created", "format": "date-time" }, "deletionProtectionEnabled": { "type": "boolean", "title": "Deletion protection", "description": "Whether deletion protection is enabled for the table" }, "encryptionType": { "type": "string", "title": "Encryption type", "description": "The type of server-side encryption (AES256 or KMS)" }, "region": { "type": "string", "title": "Region", "description": "The AWS region where the table resides" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the table", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **DynamoDB Table mapping configuration (click to expand)** ``` resources: - kind: AWS::DynamoDB::Table selector: query: 'true' includeActions: - GetTableTagsAction port: entity: mappings: identifier: .Properties.TableArn title: .Properties.TableName blueprint: '"dynamodbTable"' properties: tableArn: .Properties.TableArn tableStatus: .Properties.TableStatus billingMode: .Properties.BillingModeSummary.BillingMode itemCount: .Properties.ItemCount tableSizeBytes: .Properties.TableSizeBytes creationDateTime: .Properties.CreationDateTime deletionProtectionEnabled: .Properties.DeletionProtectionEnabled encryptionType: .Properties.SSEDescription.SSEType region: .__ExtraContext.Region tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about DynamoDB table properties, refer to the [AWS DynamoDB API documentation](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/Welcome.html). --- # Amazon Elastic Compute Cloud (EC2) ## AWS::EC2::Instance[​](#awsec2instance "Direct link to AWS::EC2::Instance") The following example demonstrates how to ingest your AWS EC2 instances to Port. #### EC2 Instance supported actions[​](#ec2-instance-supported-actions "Direct link to EC2 Instance supported actions") The table below summarizes the available actions for ingesting Amazon EC2 Instance resources in Port: | Action | Description | Type | Required AWS Permission | | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -------- | ---------------------------- | | [DescribeInstancesAction](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html) | Discover EC2 instances and retrieve detailed configuration data. | Default | `ec2:DescribeInstances` | | [GetInstanceStatusAction](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstanceStatus.html) | Retrieve instance status information. | Optional | `ec2:DescribeInstanceStatus` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **EC2 Instance blueprint (click to expand)** Create in Port ``` { "identifier": "ec2Instance", "description": "This blueprint represents an AWS EC2 instance in our software catalog", "title": "EC2 instance", "icon": "AWS", "schema": { "properties": { "instanceArn": { "type": "string", "title": "Instance ARN" }, "instanceId": { "type": "string", "title": "Instance ID" }, "instanceType": { "type": "string", "title": "Instance type" }, "availabilityZone": { "type": "string", "title": "Availability zone" }, "tags": { "type": "array", "title": "Tags", "items": { "type": "object", "properties": { "Key": { "type": "string" }, "Value": { "type": "string" } } } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **EC2 Instance mapping configuration (click to expand)** ``` resources: - kind: AWS::EC2::Instance selector: query: 'true' includeActions: # Optional: Include up to 3 additional actions for more properties # Choose based on which properties you need most # - GetInstanceStatusAction port: entity: mappings: identifier: .Properties.InstanceId title: .Properties.InstanceId blueprint: '"ec2Instance"' properties: instanceArn: .Properties.InstanceArn instanceId: .Properties.InstanceId instanceType: .Properties.InstanceType availabilityZone: .Properties.AvailabilityZone tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about EC2 instance properties, refer to the [AWS EC2 API documentation](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Welcome.html). ## AWS::EC2::Volume[​](#awsec2volume "Direct link to AWS::EC2::Volume") The following example demonstrates how to ingest your AWS EBS (Elastic Block Store) volumes to Port. #### EBS Volume supported actions[​](#ebs-volume-supported-actions "Direct link to EBS Volume supported actions") The table below summarizes the available actions for ingesting Amazon EBS Volume resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ----------------------------- | | [DescribeVolumesAction](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVolumes.html) | Discover EBS volumes and retrieve core configuration data such as type, size, IOPS, throughput, availability zone, state, encryption status, and tags. | Default | `ec2:DescribeVolumes` | | [DescribeVolumeAttributeAction](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVolumeAttribute.html) | Retrieve the `AutoEnableIO` attribute for each volume. Required to populate the `autoEnableIO` property. | Optional | `ec2:DescribeVolumeAttribute` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **EBS Volume blueprint (click to expand)** Create in Port ``` { "identifier": "ebsVolume", "title": "EBS Volume", "icon": "AWS", "schema": { "properties": { "volumeType": { "type": "string", "title": "Volume Type", "description": "The volume type (gp2, gp3, io1, io2, st1, sc1, standard)" }, "size": { "type": "number", "title": "Size (GiB)", "description": "The size of the volume in GiB" }, "iops": { "type": "number", "title": "IOPS", "description": "The number of I/O operations per second" }, "throughput": { "type": "number", "title": "Throughput (MiB/s)", "description": "The throughput provisioned for the volume in MiB/s" }, "availabilityZone": { "type": "string", "title": "Availability Zone", "description": "The Availability Zone in which the volume resides" }, "state": { "type": "string", "title": "State", "description": "The current state of the volume", "enum": [ "creating", "available", "in-use", "deleting", "deleted", "error" ] }, "creationDate": { "type": "string", "title": "Creation Date", "description": "The date and time the volume was created", "format": "date-time" }, "encrypted": { "type": "boolean", "title": "Encrypted", "description": "Indicates whether the volume is encrypted" }, "autoEnableIO": { "type": "boolean", "title": "Auto Enable IO", "description": "Indicates whether the volume is auto-enabled for I/O operations" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the volume", "items": { "type": "object" } }, "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the volume" } }, "required": [] }, "calculationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **EBS Volume mapping configuration (click to expand)** ``` resources: - kind: AWS::EC2::Volume selector: query: 'true' includeActions: # Optional: Include up to 3 additional actions for more properties # Choose based on which properties you need most # - DescribeVolumeAttributeAction port: entity: mappings: identifier: .Properties.VolumeId title: '(.Properties.Tags // [] | map(select(.Key == "Name")) | first | .Value) // .Properties.VolumeId' blueprint: '"ebsVolume"' properties: volumeType: .Properties.VolumeType size: .Properties.Size iops: .Properties.Iops throughput: .Properties.Throughput availabilityZone: .Properties.AvailabilityZone state: .Properties.State creationDate: .Properties.CreateTime encrypted: .Properties.Encrypted autoEnableIO: .Properties.AutoEnableIO tags: .Properties.Tags arn: '"arn:aws:ec2:" + .__ExtraContext.Region + ":" + .__ExtraContext.AccountId + ":volume/" + .Properties.VolumeId' relations: account: .__ExtraContext.AccountId ``` For more details about EBS volume properties, refer to the [AWS EC2 API documentation](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/Welcome.html). --- # Amazon Elastic Container Registry (ECR) ## AWS::ECR::Repository[​](#awsecrrepository "Direct link to AWS::ECR::Repository") The following example demonstrates how to ingest your AWS ECR repositories to Port. #### ECR Repository supported actions[​](#ecr-repository-supported-actions "Direct link to ECR Repository supported actions") The table below summarizes the available actions for ingesting Amazon ECR Repository resources in Port: | Action | Description | Type | Required AWS Permission | | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | -------- | -------------------------- | | [DescribeRepositoriesAction](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_DescribeRepositories.html) | Discover ECR repositories and retrieve detailed configuration data. | Default | `ecr:DescribeRepositories` | | [GetRepositoryPolicyAction](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_GetRepositoryPolicy.html) | Retrieve the repository policy for ECR repositories. | Optional | `ecr:GetRepositoryPolicy` | | [GetLifecyclePolicyAction](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_GetLifecyclePolicy.html) | Retrieve the lifecycle policy for ECR repositories. | Optional | `ecr:GetLifecyclePolicy` | | [ListTagsForResourceAction](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_ListTagsForResource.html) | Retrieve tags associated with ECR repositories. | Optional | `ecr:ListTagsForResource` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **ECR Repository blueprint (click to expand)** Create in Port ``` { "identifier": "ecrRepository", "description": "This blueprint represents an AWS ECR repository in our software catalog", "title": "ECR Repository", "icon": "AWS", "schema": { "properties": { "repositoryUri": { "type": "string", "title": "Repository URI" }, "registryId": { "type": "string", "title": "Registry ID" }, "createdAt": { "type": "string", "title": "Created At" }, "imageTagMutability": { "type": "string", "title": "Image Tag Mutability" }, "imageScanningConfiguration": { "type": "object", "title": "Image Scanning Configuration" }, "encryptionConfiguration": { "type": "object", "title": "Encryption Configuration" }, "lifecyclePolicy": { "type": "object", "title": "Lifecycle Policy" }, "repositoryPolicyText": { "type": "string", "title": "Repository Policy Text" }, "tags": { "type": "array", "title": "Tags", "items": { "type": "object", "properties": { "Key": { "type": "string" }, "Value": { "type": "string" } } } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **ECR Repository mapping configuration (click to expand)** ``` resources: - kind: AWS::ECR::Repository selector: query: 'true' includeActions: # Optional: Include additional actions for more properties # - GetRepositoryPolicyAction # - GetLifecyclePolicyAction # - ListTagsForResourceAction port: entity: mappings: identifier: .Properties.RepositoryArn title: .Properties.RepositoryName blueprint: '"ecrRepository"' properties: repositoryUri: .Properties.RepositoryUri registryId: .Properties.RegistryId createdAt: .Properties.CreatedAt imageTagMutability: .Properties.ImageTagMutability imageScanningConfiguration: .Properties.ImageScanningConfiguration encryptionConfiguration: .Properties.EncryptionConfiguration lifecyclePolicy: .Properties.LifecyclePolicy repositoryPolicyText: .Properties.RepositoryPolicyText tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` #### Example response[​](#example-response "Direct link to Example response") The following example shows the structure of the response data that Port processes for an ECR repository. **ECR Repository response example (click to expand)** ``` { "Type": "AWS::ECR::Repository", "Properties": { "RepositoryName": "testing-actions", "RepositoryArn": "arn:aws:ecr:us-west-2:123456789012:repository/testing-actions", "RepositoryUri": "123456789012.dkr.ecr.us-west-2.amazonaws.com/testing-actions", "RegistryId": "123456789012", "CreatedAt": "2024-05-14T12:12:40.109000+03:00", "ImageTagMutability": "MUTABLE", "ImageScanningConfiguration": { "scanOnPush": false }, "EncryptionConfiguration": { "encryptionType": "AES256" }, "RepositoryPolicyText": "{\n \"Version\" : \"2012-10-17\",\n \"Statement\" : [ {\n \"Sid\" : \"new statement\",\n \"Effect\" : \"Allow\",\n \"Principal\" : {\n \"AWS\" : \"arn:aws:iam::123456789012:role/admin-access\"\n },\n \"Action\" : \"ecr:DescribeRepositories\"\n } ]\n}", "LifecyclePolicy": { "registryId": "123456789012", "repositoryName": "testing-actions", "lifecyclePolicyText": "{\"rules\":[{\"rulePriority\":1,\"selection\":{\"tagStatus\":\"tagged\",\"tagPatternList\":[\"testimagetag\"],\"countType\":\"sinceImagePushed\",\"countUnit\":\"days\",\"countNumber\":1},\"action\":{\"type\":\"expire\"}}]}", "lastEvaluatedAt": "1970-01-01T03:00:00+03:00" }, "Tags": [ { "Key": "Environment", "Value": "Production" }, { "Key": "Team", "Value": "DevOps" } ] }, "__ExtraContext": { "AccountId": "123456789012", "Region": "us-west-2" } } ``` For more details about ECR repository properties, refer to the [AWS ECR API documentation](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/Welcome.html). --- # Amazon Elastic Container Service (ECS) ## AWS::ECS::Cluster[​](#awsecscluster "Direct link to AWS::ECS::Cluster") The following example demonstrates how to ingest your AWS ECS clusters to Port. #### ECS Cluster supported actions[​](#ecs-cluster-supported-actions "Direct link to ECS Cluster supported actions") The table below summarizes the available actions for ingesting Amazon ECS Cluster resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | ------- | ------------------------------------------ | | [DescribeClustersAction](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeClusters.html) | Discover ECS clusters and retrieve detailed configuration data. | Default | `ecs:ListClusters`, `ecs:DescribeClusters` | All properties available by default ECS clusters expose their key properties via the default DescribeClusters action. You can use the following Port blueprint definitions and integration configuration: **ECS Cluster blueprint (click to expand)** Create in Port ``` { "identifier": "ecsCluster", "description": "This blueprint represents an AWS ECS cluster in our software catalog", "title": "ECS cluster", "icon": "AWS", "schema": { "properties": { "status": { "type": "string", "title": "Status" }, "runningTasksCount": { "type": "number", "title": "Running tasks count" }, "activeServicesCount": { "type": "number", "title": "Active services count" }, "pendingTasksCount": { "type": "number", "title": "Pending tasks count" }, "registeredContainerInstancesCount": { "type": "number", "title": "Registered container instances count" }, "capacityProviders": { "type": "array", "title": "Capacity providers" }, "clusterArn": { "type": "string", "title": "Cluster ARN" }, "tags": { "type": "array", "title": "Tags", "items": { "type": "object", "properties": { "Key": { "type": "string" }, "Value": { "type": "string" } } } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **ECS Cluster mapping configuration (click to expand)** ``` resources: - kind: AWS::ECS::Cluster selector: query: 'true' # includeActions: No optional actions available for ECS clusters # All properties are included by default via DescribeClustersAction port: entity: mappings: identifier: .Properties.ClusterArn title: .Properties.ClusterName blueprint: '"ecsCluster"' properties: status: .Properties.Status runningTasksCount: .Properties.RunningTasksCount activeServicesCount: .Properties.ActiveServicesCount pendingTasksCount: .Properties.PendingTasksCount registeredContainerInstancesCount: .Properties.RegisteredContainerInstancesCount capacityProviders: .Properties.CapacityProviders clusterArn: .Properties.ClusterArn tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` ## AWS::ECS::Service[​](#awsecsservice "Direct link to AWS::ECS::Service") The following example demonstrates how to ingest your AWS ECS services to Port. #### ECS Service supported actions[​](#ecs-service-supported-actions "Direct link to ECS Service supported actions") The table below summarizes the available actions for ingesting Amazon ECS Service resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------- | ------------------------------------------ | | [DescribeServicesAction](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeServices.html) | Discover ECS services within your clusters and retrieve detailed configuration data. | Default | `ecs:ListServices`, `ecs:DescribeServices` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **ECS Service blueprint (click to expand)** Create in Port ``` { "identifier": "ecsService", "title": "ECS Service", "icon": "AWS", "schema": { "properties": { "serviceName": { "type": "string", "title": "Service Name", "description": "The name of the ECS service" }, "serviceArn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the service" }, "clusterArn": { "type": "string", "title": "Cluster ARN", "description": "The ARN of the cluster that hosts the service" }, "taskDefinition": { "type": "string", "title": "Task Definition", "description": "The ARN of the task definition associated with the service" }, "desiredCount": { "type": "number", "title": "Desired Count", "description": "The desired number of tasks for the service" }, "runningCount": { "type": "number", "title": "Running Count", "description": "The number of tasks that are currently running" }, "launchType": { "type": "string", "title": "Launch Type", "description": "The launch type of the service", "enum": ["EC2", "FARGATE", "EXTERNAL"] }, "status": { "type": "string", "title": "Status", "description": "The current status of the service", "enum": ["ACTIVE", "DRAINING", "INACTIVE"] } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false }, "cluster": { "title": "Cluster", "target": "ecsCluster", "required": false, "many": false } } } ``` **ECS Service mapping configuration (click to expand)** ``` resources: - kind: AWS::ECS::Service selector: query: 'true' port: entity: mappings: identifier: .Properties.ServiceArn title: .Properties.ServiceName blueprint: '"ecsService"' properties: serviceName: .Properties.ServiceName serviceArn: .Properties.ServiceArn clusterArn: .Properties.ClusterArn taskDefinition: .Properties.TaskDefinition desiredCount: .Properties.DesiredCount runningCount: .Properties.RunningCount launchType: .Properties.LaunchType status: .Properties.Status relations: account: .__ExtraContext.AccountId cluster: .Properties.ClusterArn ``` ## AWS::ECS::TaskDefinition[​](#awsecstaskdefinition "Direct link to AWS::ECS::TaskDefinition") The following example demonstrates how to ingest your AWS ECS task definitions to Port. #### ECS Task Definition supported actions[​](#ecs-task-definition-supported-actions "Direct link to ECS Task Definition supported actions") The table below summarizes the available actions for ingesting Amazon ECS Task Definition resources in Port: | Action | Description | Type | Required AWS Permission | | -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------- | ---------------------------- | | [ListTaskDefinitionsAction](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ListTaskDefinitions.html) | List all active task definition ARNs in the account. | Default | `ecs:ListTaskDefinitions` | | [DescribeTaskDefinitionsAction](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTaskDefinition.html) | Fetch full container configuration details for each task definition. | Default | `ecs:DescribeTaskDefinition` | All properties available by default ECS task definitions expose all key properties via the default ListTaskDefinitions and DescribeTaskDefinitions actions. You can use the following Port blueprint definitions and integration configuration: **ECS Task Definition blueprint (click to expand)** ``` [ { "identifier": "ecsTaskDefinition", "title": "ECS Task Definition", "icon": "AWS", "schema": { "properties": { "taskDefinitionArn": { "type": "string", "title": "Task Definition ARN", "description": "The full ARN of the task definition" }, "family": { "type": "string", "title": "Family", "description": "The family name of the task definition" }, "revision": { "type": "number", "title": "Revision", "description": "The revision number of the task definition" }, "status": { "type": "string", "title": "Status", "description": "The status of the task definition", "enum": ["ACTIVE", "INACTIVE", "DELETE_IN_PROGRESS"] }, "containerDefinitions": { "type": "array", "title": "Container Definitions", "description": "The container definitions that describe the containers in the task", "items": { "type": "object" } }, "cpu": { "type": "string", "title": "CPU", "description": "The number of CPU units used by the task (e.g. 256, 512, 1024)" }, "memory": { "type": "string", "title": "Memory", "description": "The amount of memory (in MiB) used by the task (e.g. 512, 1024, 2048)" }, "networkMode": { "type": "string", "title": "Network Mode", "description": "The Docker networking mode to use for the containers", "enum": ["bridge", "host", "awsvpc", "none"] }, "requiresCompatibilities": { "type": "array", "title": "Requires Compatibilities", "description": "The task launch types the task definition was validated against", "items": { "type": "string" } }, "taskRoleArn": { "type": "string", "title": "Task Role ARN", "description": "The ARN of the IAM role that grants containers permission to call AWS APIs" }, "executionRoleArn": { "type": "string", "title": "Execution Role ARN", "description": "The ARN of the task execution IAM role" }, "volumes": { "type": "array", "title": "Volumes", "description": "The list of data volume definitions for the task", "items": { "type": "object" } }, "placementConstraints": { "type": "array", "title": "Placement Constraints", "description": "The placement constraint objects to use for tasks", "items": { "type": "object" } }, "tags": { "type": "array", "title": "Tags", "description": "The tags associated with the task definition", "items": { "type": "object" } }, "registeredAt": { "type": "string", "title": "Registered At", "format": "date-time", "description": "The timestamp when the task definition was registered" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ] ``` **ECS Task Definition mapping configuration (click to expand)** ``` resources: - kind: AWS::ECS::TaskDefinition selector: query: 'true' port: entity: mappings: identifier: .Properties.TaskDefinitionArn title: .Properties.Family + ":" + (.Properties.Revision | tostring) blueprint: '"ecsTaskDefinition"' properties: taskDefinitionArn: .Properties.TaskDefinitionArn family: .Properties.Family revision: .Properties.Revision status: .Properties.Status containerDefinitions: .Properties.ContainerDefinitions cpu: .Properties.Cpu memory: .Properties.Memory networkMode: .Properties.NetworkMode requiresCompatibilities: .Properties.RequiresCompatibilities taskRoleArn: .Properties.TaskRoleArn executionRoleArn: .Properties.ExecutionRoleArn volumes: .Properties.Volumes placementConstraints: .Properties.PlacementConstraints tags: .Properties.Tags registeredAt: .Properties.RegisteredAt relations: account: .__ExtraContext.AccountId ``` For more details about ECS cluster properties, refer to the [AWS ECS API documentation](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/Welcome.html). --- # Amazon Elastic Kubernetes Service (EKS) ## AWS::EKS::Cluster[​](#awsekscluster "Direct link to AWS::EKS::Cluster") The following example demonstrates how to ingest your AWS EKS clusters to Port. #### EKS Cluster supported actions[​](#eks-cluster-supported-actions "Direct link to EKS Cluster supported actions") The table below summarizes the available actions for ingesting Amazon EKS Cluster resources in Port: | Action | Description | Type | Required AWS Permission | | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ------- | ---------------------------------------- | | [DescribeClusterAction](https://docs.aws.amazon.com/eks/latest/APIReference/API_DescribeCluster.html) | Retrieve detailed configuration data for each cluster. | Default | `eks:DescribeCluster` `eks:ListClusters` | All properties available by default EKS clusters expose their key properties via the default DescribeCluster action. You can use the following Port blueprint definitions and integration configuration: **EKS Cluster blueprint (click to expand)** Create in Port ``` { "identifier": "eksCluster", "title": "EKS Cluster", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the EKS cluster" }, "name": { "type": "string", "title": "Name", "description": "The name of the EKS cluster" }, "version": { "type": "string", "title": "Kubernetes Version", "description": "The Kubernetes version of the EKS cluster" }, "status": { "type": "string", "title": "Status", "description": "The status of the EKS cluster" }, "endpoint": { "type": "string", "title": "Endpoint", "description": "The endpoint URL for the EKS cluster" }, "roleArn": { "type": "string", "title": "Role ARN", "description": "The IAM role ARN associated with the EKS cluster" }, "tags": { "type": "object", "title": "Tags", "description": "Tags associated with the EKS cluster", "additionalProperties": { "type": "string" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **EKS Cluster mapping configuration (click to expand)** ``` resources: - kind: AWS::EKS::Cluster selector: query: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Properties.Name blueprint: '"eksCluster"' properties: arn: .Properties.Arn name: .Properties.Name version: .Properties.Version status: .Properties.Status endpoint: .Properties.Endpoint roleArn: .Properties.RoleArn tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about EKS cluster properties, refer to the [AWS EKS API documentation](https://docs.aws.amazon.com/eks/latest/APIReference/Welcome.html). --- # Amazon ElastiCache ## AWS::ElastiCache::Cluster[​](#awselasticachecluster "Direct link to AWS::ElastiCache::Cluster") The following example demonstrates how to ingest your AWS ElastiCache clusters to Port. #### ElastiCache Cluster supported actions[​](#elasticache-cluster-supported-actions "Direct link to ElastiCache Cluster supported actions") The table below summarizes the available actions for ingesting Amazon ElastiCache Cluster resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -------- | ----------------------------------- | | [DescribeCacheClustersAction](https://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/API_DescribeCacheClusters.html) | Discover all ElastiCache clusters and retrieve their configuration (nodes, engine, status). | Default | `elasticache:DescribeCacheClusters` | | [ListTagsForResourceAction](https://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/API_ListTagsForResource.html) | Retrieve tags for each cluster. | Optional | `elasticache:ListTagsForResource` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **ElastiCache Cluster blueprint (click to expand)** ``` [ { "identifier": "elasticacheCluster", "title": "ElastiCache Cluster", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the cluster" }, "cacheClusterId": { "type": "string", "title": "Cluster ID", "description": "The unique identifier of the cache cluster" }, "cacheClusterStatus": { "type": "string", "title": "Status", "description": "The current status of the cluster", "enum": ["available", "creating", "deleted", "deleting", "incompatible-network", "modifying", "rebooting cluster nodes", "restore-failed", "snapshotting"] }, "cacheNodeType": { "type": "string", "title": "Node Type", "description": "The compute and memory capacity of the nodes (e.g., cache.t4g.micro, cache.r6g.large)" }, "engine": { "type": "string", "title": "Engine", "description": "The cache engine (redis or memcached)" }, "engineVersion": { "type": "string", "title": "Engine Version", "description": "The version of the cache engine" }, "numCacheNodes": { "type": "number", "title": "Number of Nodes", "description": "The number of cache nodes in the cluster" }, "preferredAvailabilityZone": { "type": "string", "title": "Availability Zone", "description": "The preferred availability zone for the cluster" }, "cacheClusterCreateTime": { "type": "string", "title": "Created At", "description": "The date and time when the cluster was created", "format": "date-time" }, "preferredMaintenanceWindow": { "type": "string", "title": "Maintenance Window", "description": "The weekly time range for maintenance" }, "cacheParameterGroup": { "type": "object", "title": "Parameter Group", "description": "The cache parameter group associated with the cluster" }, "cacheSubnetGroupName": { "type": "string", "title": "Subnet Group", "description": "The name of the cache subnet group" }, "cacheNodes": { "type": "array", "title": "Cache Nodes", "description": "The list of cache nodes in the cluster", "items": { "type": "object" } }, "autoMinorVersionUpgrade": { "type": "boolean", "title": "Auto Minor Version Upgrade", "description": "Whether minor version upgrades are applied automatically" }, "securityGroups": { "type": "array", "title": "Security Groups", "description": "The VPC security groups associated with the cluster", "items": { "type": "object" } }, "replicationGroupId": { "type": "string", "title": "Replication Group ID", "description": "The replication group this cluster belongs to" }, "snapshotRetentionLimit": { "type": "number", "title": "Snapshot Retention Limit", "description": "Number of days snapshots are retained (0 means backups are disabled)" }, "snapshotWindow": { "type": "string", "title": "Snapshot Window", "description": "The daily time range for automatic snapshots" }, "authTokenEnabled": { "type": "boolean", "title": "Auth Token Enabled", "description": "Whether Redis AUTH is enabled" }, "transitEncryptionEnabled": { "type": "boolean", "title": "Transit Encryption", "description": "Whether in-transit encryption is enabled" }, "atRestEncryptionEnabled": { "type": "boolean", "title": "At-Rest Encryption", "description": "Whether at-rest encryption is enabled" }, "networkType": { "type": "string", "title": "Network Type", "description": "The network type (ipv4, ipv6, or dual_stack)" }, "ipDiscovery": { "type": "string", "title": "IP Discovery", "description": "The IP discovery type" }, "logDeliveryConfigurations": { "type": "array", "title": "Log Delivery Configurations", "description": "The log delivery configurations", "items": { "type": "object" } }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the cluster", "items": { "type": "object", "properties": { "Key": { "type": "string" }, "Value": { "type": "string" } } } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ] ``` **ElastiCache Cluster mapping configuration (click to expand)** ``` resources: - kind: AWS::ElastiCache::Cluster selector: query: 'true' port: entity: mappings: identifier: .Properties.ARN title: .Properties.CacheClusterId blueprint: '"elasticacheCluster"' properties: arn: .Properties.ARN cacheClusterId: .Properties.CacheClusterId cacheClusterStatus: .Properties.CacheClusterStatus cacheNodeType: .Properties.CacheNodeType engine: .Properties.Engine engineVersion: .Properties.EngineVersion numCacheNodes: .Properties.NumCacheNodes preferredAvailabilityZone: .Properties.PreferredAvailabilityZone cacheClusterCreateTime: .Properties.CacheClusterCreateTime preferredMaintenanceWindow: .Properties.PreferredMaintenanceWindow cacheParameterGroup: .Properties.CacheParameterGroup cacheSubnetGroupName: .Properties.CacheSubnetGroupName cacheNodes: .Properties.CacheNodes autoMinorVersionUpgrade: .Properties.AutoMinorVersionUpgrade securityGroups: .Properties.SecurityGroups replicationGroupId: .Properties.ReplicationGroupId snapshotRetentionLimit: .Properties.SnapshotRetentionLimit snapshotWindow: .Properties.SnapshotWindow authTokenEnabled: .Properties.AuthTokenEnabled transitEncryptionEnabled: .Properties.TransitEncryptionEnabled atRestEncryptionEnabled: .Properties.AtRestEncryptionEnabled networkType: .Properties.NetworkType ipDiscovery: .Properties.IpDiscovery logDeliveryConfigurations: .Properties.LogDeliveryConfigurations tags: .Properties.TagList relations: account: .__ExtraContext.AccountId ``` For more details about ElastiCache cluster properties, refer to the [AWS ElastiCache API documentation](https://docs.aws.amazon.com/AmazonElastiCache/latest/APIReference/Welcome.html). --- # Amazon Managed Streaming for Apache Kafka (MSK) ## AWS::MSK::Cluster[​](#awsmskcluster "Direct link to AWS::MSK::Cluster") The following example demonstrates how to ingest your AWS MSK clusters to Port. #### MSK Cluster supported actions[​](#msk-cluster-supported-actions "Direct link to MSK Cluster supported actions") The table below summarizes the available actions for ingesting Amazon MSK Cluster resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------ | --------------------------------------------------------------- | ------- | ----------------------- | | [ListClustersAction](https://docs.aws.amazon.com/msk/1.0/apireference/clusters.html) | Discover MSK clusters and retrieve detailed configuration data. | Default | `kafka:ListClusters` | You can use the following Port blueprint definitions and integration configuration: **MSK Cluster blueprint (click to expand)** ``` [ { "identifier": "mskCluster", "title": "MSK Cluster", "icon": "AWS", "schema": { "properties": { "clusterArn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the MSK cluster" }, "state": { "type": "string", "title": "State", "description": "The current state of the cluster", "enum": [ "ACTIVE", "CREATING", "DELETING", "FAILED", "HEALING", "MAINTENANCE", "REBOOTING_BROKER", "UPDATING" ] }, "kafkaVersion": { "type": "string", "title": "Kafka Version", "description": "The version of Apache Kafka" }, "numberOfBrokerNodes": { "type": "number", "title": "Number of Broker Nodes", "description": "The number of broker nodes in the cluster" }, "instanceType": { "type": "string", "title": "Instance Type", "description": "The type of broker instances" }, "enhancedMonitoring": { "type": "string", "title": "Enhanced Monitoring", "description": "The level of enhanced monitoring" }, "clientAuthentication": { "type": "object", "title": "Client Authentication", "description": "Client authentication settings (IAM, SASL, TLS)" }, "encryptionInTransit": { "type": "string", "title": "Encryption In Transit", "description": "Encryption setting for data in transit between clients and brokers" }, "creationTime": { "type": "string", "title": "Creation Time", "description": "The time when the cluster was created", "format": "date-time" }, "tags": { "type": "object", "title": "Tags", "description": "Tags associated with the cluster" } }, "required": [] }, "calculationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ] ``` **MSK Cluster mapping configuration (click to expand)** ``` resources: - kind: AWS::MSK::Cluster selector: query: 'true' port: entity: mappings: identifier: .Properties.ClusterArn title: .Properties.ClusterName blueprint: '"mskCluster"' properties: clusterArn: .Properties.ClusterArn state: .Properties.State kafkaVersion: .Properties.CurrentBrokerSoftwareInfo.KafkaVersion numberOfBrokerNodes: .Properties.NumberOfBrokerNodes instanceType: .Properties.BrokerNodeGroupInfo.InstanceType enhancedMonitoring: .Properties.EnhancedMonitoring clientAuthentication: .Properties.ClientAuthentication encryptionInTransit: .Properties.EncryptionInfo.EncryptionInTransit.ClientBroker creationTime: .Properties.CreationTime tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about MSK cluster properties, refer to the [AWS MSK API documentation](https://docs.aws.amazon.com/msk/latest/developerguide/what-is-msk.html). ## AWS::MSK::ServerlessCluster[​](#awsmskserverlesscluster "Direct link to AWS::MSK::ServerlessCluster") The following example demonstrates how to ingest your AWS MSK serverless clusters to Port. #### MSK Serverless Cluster supported actions[​](#msk-serverless-cluster-supported-actions "Direct link to MSK Serverless Cluster supported actions") The table below summarizes the available actions for ingesting Amazon MSK Serverless Cluster resources in Port: | Action | Description | Type | Required AWS Permission | | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------- | ----------------------- | | [ListMskServerlessClustersAction](https://docs.aws.amazon.com/msk/1.0/apireference/clusters-v2.html) | Discover MSK serverless clusters and retrieve configuration data such as VPC settings, client authentication, and cluster state. | Default | `kafka:ListClustersV2` | All properties available by default MSK serverless clusters expose their key properties via the default ListMskServerlessClustersAction. You can use the following Port blueprint definitions and integration configuration: **MSK Serverless Cluster blueprint (click to expand)** Create in Port ``` { "identifier": "mskServerlessCluster", "title": "MSK Serverless Cluster", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the MSK serverless cluster" }, "clusterName": { "type": "string", "title": "Cluster Name", "description": "The name of the MSK serverless cluster" }, "state": { "type": "string", "title": "State", "description": "The current state of the cluster", "enum": [ "ACTIVE", "CREATING", "DELETING", "FAILED", "HEALING", "MAINTENANCE", "REBOOTING_BROKER", "UPDATING" ] }, "creationTime": { "type": "string", "title": "Creation Time", "description": "The date and time when the cluster was created", "format": "date-time" }, "currentVersion": { "type": "string", "title": "Current Version", "description": "The current version of the MSK cluster" }, "vpcConfigs": { "type": "array", "title": "VPC Configs", "description": "The VPC configuration for the serverless cluster", "items": { "type": "object" } }, "clientAuthentication": { "type": "object", "title": "Client Authentication", "description": "The client authentication configuration for the cluster" }, "tags": { "type": "object", "title": "Tags", "description": "Tags associated with the cluster" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **MSK Serverless Cluster mapping configuration (click to expand)** ``` resources: - kind: AWS::MSK::ServerlessCluster selector: query: 'true' port: entity: mappings: identifier: .Properties.ClusterArn title: .Properties.ClusterName blueprint: '"mskServerlessCluster"' properties: arn: .Properties.ClusterArn clusterName: .Properties.ClusterName state: .Properties.State creationTime: .Properties.CreationTime currentVersion: .Properties.CurrentVersion vpcConfigs: .Properties.Serverless.VpcConfigs clientAuthentication: .Properties.Serverless.ClientAuthentication tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about MSK serverless cluster properties, refer to the [AWS MSK API documentation](https://docs.aws.amazon.com/msk/latest/developerguide/what-is-msk.html). --- # Amazon MemoryDB ## AWS::MemoryDB::User[​](#awsmemorydbuser "Direct link to AWS::MemoryDB::User") The following example demonstrates how to ingest your AWS MemoryDB users to Port. #### MemoryDB User supported actions[​](#memorydb-user-supported-actions "Direct link to MemoryDB User supported actions") The table below summarizes the available actions for ingesting Amazon MemoryDB User resources in Port: | Action | Description | Type | Required AWS Permission | | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------ | | [DescribeMemoryDbUsersAction](https://docs.aws.amazon.com/memorydb/latest/APIReference/API_DescribeUsers.html) | Discover MemoryDB users and retrieve configuration details such as access string, ACL names, and authentication mode. | Default | `memorydb:DescribeUsers` | | [ListTagsForMemoryDbUserAction](https://docs.aws.amazon.com/memorydb/latest/APIReference/API_ListTags.html) | Retrieve tags for each MemoryDB user. | Optional | `memorydb:ListTags` | Optional properties note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. ### Supported regions[​](#supported-regions "Direct link to Supported regions") * `ap-east-1`. * `ap-northeast-1`. * `ap-northeast-2`. * `ap-south-1`. * `ap-southeast-1`. * `ap-southeast-2`. * `ca-central-1`. * `eu-central-1`. * `eu-north-1`. * `eu-south-1`. * `eu-south-2`. * `eu-west-1`. * `eu-west-2`. * `eu-west-3`. * `sa-east-1`. * `us-east-1`. * `us-east-2`. * `us-west-1`. * `us-west-2`. * `us-gov-east-1`. * `us-gov-west-1`. You can use the following Port blueprint definitions and integration configuration: **MemoryDB User blueprint (click to expand)** Create in Port ``` { "identifier": "memoryDbUser", "title": "MemoryDB User", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the MemoryDB user" }, "status": { "type": "string", "title": "Status", "description": "The current status of the user (active, modifying, or deleting)" }, "accessString": { "type": "string", "title": "Access String", "description": "Access permissions string used for this user account" }, "aclNames": { "type": "array", "title": "ACL Names", "description": "The names of the Access Control Lists (ACLs) the user belongs to", "items": { "type": "string" } }, "minimumEngineVersion": { "type": "string", "title": "Minimum Engine Version", "description": "The minimum engine version supported for the user" }, "authentication": { "type": "object", "title": "Authentication", "description": "The authentication mode for the user" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the MemoryDB user", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **MemoryDB User mapping configuration (click to expand)** ``` resources: - kind: AWS::MemoryDB::User selector: query: 'true' port: entity: mappings: identifier: .Properties.ARN title: .Properties.Name blueprint: '"memoryDbUser"' properties: arn: .Properties.ARN status: .Properties.Status accessString: .Properties.AccessString aclNames: .Properties.ACLNames minimumEngineVersion: .Properties.MinimumEngineVersion authentication: .Properties.Authentication tags: .Properties.TagList relations: account: .__ExtraContext.AccountId ``` For more details about MemoryDB user properties, refer to the [AWS MemoryDB API documentation](https://docs.aws.amazon.com/memorydb/latest/APIReference/Welcome.html). --- # Amazon Relational Database Service (RDS) ## AWS::RDS::DBInstance[​](#awsrdsdbinstance "Direct link to AWS::RDS::DBInstance") The following example demonstrates how to ingest your AWS RDS DB instances to Port. #### RDS DB Instance supported actions[​](#rds-db-instance-supported-actions "Direct link to RDS DB Instance supported actions") The table below summarizes the available actions for ingesting Amazon RDS DB Instance resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | -------- | ------------------------- | | [DescribeDBInstancesAction](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBInstances.html) | Discover DB instances and retrieve configuration details. | Default | `rds:DescribeDBInstances` | | [ListTagsForResourceAction](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_ListTagsForResource.html) | Retrieve tags for each DB instance. | Optional | `rds:ListTagsForResource` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **RDS DB Instance blueprint (click to expand)** Create in Port ``` { "identifier": "rdsDbInstance", "title": "RDS DB Instance", "icon": "AWS", "schema": { "properties": { "dbInstanceIdentifier": { "type": "string", "title": "DB Instance Identifier", "description": "The unique identifier of the RDS DB instance" }, "dbInstanceArn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the DB instance" }, "engine": { "type": "string", "title": "Engine", "description": "The database engine type (e.g., mysql, postgres, oracle-ee)" }, "dbInstanceClass": { "type": "string", "title": "Instance Class", "description": "The compute and memory capacity of the DB instance (e.g., db.t3.micro, db.m4.xlarge)" }, "dbInstanceStatus": { "type": "string", "title": "Status", "description": "The current status of the DB instance", "enum": [ "available", "backing-up", "creating", "deleting", "failed", "incompatible-credentials", "incompatible-network", "incompatible-option-group", "incompatible-parameters", "incompatible-restore", "maintenance", "modifying", "rebooting", "renaming", "resetting-master-credentials", "restore-error", "storage-full", "storage-optimization", "upgrading" ] }, "multiAZ": { "type": "boolean", "title": "Multi-AZ", "description": "Whether the DB instance is deployed in multiple Availability Zones" }, "storageEncrypted": { "type": "boolean", "title": "Storage Encrypted", "description": "Whether the DB instance storage is encrypted" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **RDS DB Instance mapping configuration (click to expand)** ``` resources: - kind: AWS::RDS::DBInstance selector: query: 'true' port: entity: mappings: identifier: .Properties.DBInstanceArn title: .Properties.DBInstanceIdentifier blueprint: '"rdsDbInstance"' properties: dbInstanceIdentifier: .Properties.DBInstanceIdentifier dbInstanceArn: .Properties.DBInstanceArn engine: .Properties.Engine dbInstanceClass: .Properties.DBInstanceClass dbInstanceStatus: .Properties.DBInstanceStatus multiAZ: .Properties.MultiAZ storageEncrypted: .Properties.StorageEncrypted relations: account: .__ExtraContext.AccountId ``` For more details about RDS DB instance properties, refer to the [AWS RDS API documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/ProgrammingGuide.html). ## AWS::RDS::DBCluster[​](#awsrdsdbcluster "Direct link to AWS::RDS::DBCluster") The following example demonstrates how to ingest your AWS RDS DB clusters (for example, Aurora clusters) to Port. #### RDS DB Cluster supported actions[​](#rds-db-cluster-supported-actions "Direct link to RDS DB Cluster supported actions") The table below summarizes the available actions for ingesting Amazon RDS DB Cluster resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------- | ------------------------- | | [DescribeDBClustersAction](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_DescribeDBClusters.html) | Discover DB clusters and retrieve configuration details such as engine mode, endpoints, and cluster members. | Default | `rds:DescribeDBClusters` | | [ListTagsForResourceAction](https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_ListTagsForResource.html) | Retrieve tags for each DB cluster. | Optional | `rds:ListTagsForResource` | Optional properties note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **RDS DB Cluster blueprint (click to expand)** Create in Port ``` { "identifier": "rdsDbCluster", "title": "RDS DB Cluster", "icon": "AWS", "schema": { "properties": { "activityStreamStatus": { "type": "string", "title": "Activity Stream Status", "description": "The status of the database activity stream" }, "associatedRoles": { "type": "array", "title": "Associated Roles", "description": "The IAM roles associated with the DB cluster", "items": { "type": "object" } }, "autoMinorVersionUpgrade": { "type": "boolean", "title": "Auto Minor Version Upgrade", "description": "Whether minor version upgrades are applied automatically" }, "availabilityZones": { "type": "array", "title": "Availability Zones", "description": "The Availability Zones where the DB cluster is deployed", "items": { "type": "string" } }, "backupRetentionPeriod": { "type": "number", "title": "Backup Retention Period", "description": "The number of days to retain automated backups" }, "clusterCreateTime": { "type": "string", "title": "Cluster Create Time", "description": "The time when the DB cluster was created" }, "copyTagsToSnapshot": { "type": "boolean", "title": "Copy Tags To Snapshot", "description": "Whether to copy tags to snapshots" }, "crossAccountClone": { "type": "boolean", "title": "Cross Account Clone", "description": "Whether cross-account cloning is enabled" }, "databaseName": { "type": "string", "title": "Database Name", "description": "The name of the initial database" }, "dbClusterArn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the DB cluster" }, "dbClusterIdentifier": { "type": "string", "title": "DB Cluster Identifier", "description": "The unique identifier of the RDS DB cluster" }, "dbClusterMembers": { "type": "array", "title": "DB Cluster Members", "description": "The DB instances that belong to the cluster", "items": { "type": "object" } }, "dbClusterResourceId": { "type": "string", "title": "DB Cluster Resource ID", "description": "The unique resource identifier for the DB cluster" }, "deletionProtection": { "type": "boolean", "title": "Deletion Protection", "description": "Whether deletion protection is enabled" }, "earliestRestorableTime": { "type": "string", "title": "Earliest Restorable Time", "description": "The earliest time to which the DB cluster can be restored" }, "endpoint": { "type": "string", "title": "Writer Endpoint", "description": "The writer endpoint for the DB cluster" }, "engine": { "type": "string", "title": "Engine", "description": "The database engine type (e.g., aurora-mysql, aurora-postgresql)" }, "engineMode": { "type": "string", "title": "Engine Mode", "description": "The engine mode of the DB cluster (e.g., provisioned, serverless)" }, "engineVersion": { "type": "string", "title": "Engine Version", "description": "The version of the database engine" }, "globalWriteForwardingStatus": { "type": "string", "title": "Global Write Forwarding Status", "description": "The status of write forwarding for a secondary cluster in a global database" }, "httpEndpointEnabled": { "type": "boolean", "title": "HTTP Endpoint Enabled", "description": "Whether the HTTP endpoint (Data API) is enabled for Aurora Serverless" }, "iamDatabaseAuthenticationEnabled": { "type": "boolean", "title": "IAM Database Authentication Enabled", "description": "Whether IAM database authentication is enabled" }, "kmsKeyId": { "type": "string", "title": "KMS Key ID", "description": "The KMS key identifier for encryption" }, "latestRestorableTime": { "type": "string", "title": "Latest Restorable Time", "description": "The latest time to which the DB cluster can be restored" }, "masterUsername": { "type": "string", "title": "Master Username", "description": "The master username for the DB cluster" }, "multiAZ": { "type": "boolean", "title": "Multi-AZ", "description": "Whether the DB cluster is deployed across multiple Availability Zones" }, "networkType": { "type": "string", "title": "Network Type", "description": "The network type for the DB cluster" }, "port": { "type": "number", "title": "Port", "description": "The port number for the DB cluster" }, "preferredBackupWindow": { "type": "string", "title": "Preferred Backup Window", "description": "The preferred backup window" }, "preferredMaintenanceWindow": { "type": "string", "title": "Preferred Maintenance Window", "description": "The preferred maintenance window" }, "readerEndpoint": { "type": "string", "title": "Reader Endpoint", "description": "The reader endpoint for load-balancing read connections across cluster members" }, "status": { "type": "string", "title": "Status", "description": "The current status of the DB cluster", "enum": [ "available", "backing-up", "creating", "deleting", "failing-over", "inaccessible-encryption-credentials", "maintenance", "migrating", "modifying", "promoting", "renaming", "resetting-master-credentials", "scaling", "starting", "stopped", "stopping", "upgrading" ] }, "storageEncrypted": { "type": "boolean", "title": "Storage Encrypted", "description": "Whether the DB cluster storage is encrypted" }, "storageType": { "type": "string", "title": "Storage Type", "description": "The storage type of the DB cluster" }, "tags": { "type": "array", "title": "Tags", "description": "The tags associated with the DB cluster", "items": { "type": "object", "properties": { "Key": { "type": "string" }, "Value": { "type": "string" } } } }, "vpcSecurityGroups": { "type": "array", "title": "VPC Security Groups", "description": "The VPC security groups associated with the DB cluster", "items": { "type": "object" } }, "dbSubnetGroup": { "type": "string", "title": "DB Subnet Group", "description": "The subnet group associated with the DB cluster" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **RDS DB Cluster mapping configuration (click to expand)** ``` resources: - kind: AWS::RDS::DBCluster selector: query: 'true' port: entity: mappings: identifier: .Properties.DBClusterArn title: .Properties.DBClusterIdentifier blueprint: '"rdsDbCluster"' properties: activityStreamStatus: .Properties.ActivityStreamStatus associatedRoles: .Properties.AssociatedRoles autoMinorVersionUpgrade: .Properties.AutoMinorVersionUpgrade availabilityZones: .Properties.AvailabilityZones backupRetentionPeriod: .Properties.BackupRetentionPeriod clusterCreateTime: .Properties.ClusterCreateTime copyTagsToSnapshot: .Properties.CopyTagsToSnapshot crossAccountClone: .Properties.CrossAccountClone databaseName: .Properties.DatabaseName dbClusterArn: .Properties.DBClusterArn dbClusterIdentifier: .Properties.DBClusterIdentifier dbClusterMembers: .Properties.DBClusterMembers dbClusterResourceId: .Properties.DbClusterResourceId deletionProtection: .Properties.DeletionProtection earliestRestorableTime: .Properties.EarliestRestorableTime endpoint: .Properties.Endpoint engine: .Properties.Engine engineMode: .Properties.EngineMode engineVersion: .Properties.EngineVersion globalWriteForwardingStatus: .Properties.GlobalWriteForwardingStatus httpEndpointEnabled: .Properties.HttpEndpointEnabled iamDatabaseAuthenticationEnabled: .Properties.IAMDatabaseAuthenticationEnabled kmsKeyId: .Properties.KmsKeyId latestRestorableTime: .Properties.LatestRestorableTime masterUsername: .Properties.MasterUsername multiAZ: .Properties.MultiAZ networkType: .Properties.NetworkType port: .Properties.Port preferredBackupWindow: .Properties.PreferredBackupWindow preferredMaintenanceWindow: .Properties.PreferredMaintenanceWindow readerEndpoint: .Properties.ReaderEndpoint status: .Properties.Status storageEncrypted: .Properties.StorageEncrypted storageType: .Properties.StorageType tags: .Properties.Tags vpcSecurityGroups: .Properties.VpcSecurityGroups dbSubnetGroup: .Properties.DBSubnetGroup relations: account: .__ExtraContext.AccountId ``` For more details about RDS DB cluster properties, refer to the [AWS RDS API documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/ProgrammingGuide.html). --- # Amazon Simple Email Service (SES) ## AWS::SES::EmailIdentity[​](#awssesemailidentity "Direct link to AWS::SES::EmailIdentity") The following example demonstrates how to ingest your AWS SES email identities to Port. #### SES Email Identity supported actions[​](#ses-email-identity-supported-actions "Direct link to SES Email Identity supported actions") The table below summarizes the available actions for ingesting Amazon SES EmailIdentity resources in Port: | Action | Description | Type | Required AWS Permission | | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------- | | [ListEmailIdentitiesAction](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_ListEmailIdentities.html) | Discover all SES email identities across your AWS account. | Default | `ses:ListEmailIdentities` | | [GetEmailIdentityAction](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetEmailIdentity.html) | Retrieve detailed configuration and verification data for each email identity, including DKIM signing status and tags. | Default | `ses:GetEmailIdentity` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **SES Email Identity blueprint (click to expand)** Create in Port ``` { "identifier": "sesEmailIdentity", "title": "SES Email Identity", "icon": "AWS", "schema": { "properties": { "identityType": { "type": "string", "title": "Identity Type", "description": "The type of the email identity (EMAIL_ADDRESS or DOMAIN)" }, "verifiedForSendingStatus": { "type": "boolean", "title": "Verified For Sending Status", "description": "Indicates whether the identity is verified for sending email" }, "dkimEnabled": { "type": "boolean", "title": "DKIM Enabled", "description": "Indicates whether DKIM signing is enabled for the identity" }, "verificationStatus": { "type": "string", "title": "Verification Status", "description": "The verification status of the email identity" }, "configurationSetName": { "type": "string", "title": "Configuration Set Name", "description": "The name of the configuration set associated with the email identity" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the email identity", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **SES Email Identity mapping configuration (click to expand)** ``` resources: - kind: AWS::SES::EmailIdentity selector: query: 'true' port: entity: mappings: identifier: .Properties.IdentityName title: .Properties.IdentityName blueprint: '"sesEmailIdentity"' properties: identityType: .Properties.IdentityType verifiedForSendingStatus: .Properties.VerifiedForSendingStatus dkimEnabled: .Properties.DkimAttributes.SigningEnabled verificationStatus: .Properties.VerificationStatus configurationSetName: .Properties.ConfigurationSetName tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about SES email identity properties, refer to the [AWS SES API documentation](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetEmailIdentity.html). ## AWS::SES::ConfigurationSet[​](#awssesconfigurationset "Direct link to AWS::SES::ConfigurationSet") The following example demonstrates how to ingest your AWS SES configuration sets to Port. #### SES Configuration Set supported actions[​](#ses-configuration-set-supported-actions "Direct link to SES Configuration Set supported actions") The table below summarizes the available actions for ingesting Amazon SES ConfigurationSet resources in Port: | Action | Description | Type | Required AWS Permission | | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------- | --------------------------- | | [ListConfigurationSetsAction](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_ListConfigurationSets.html) | Discover all SES configuration sets across your AWS account. | Default | `ses:ListConfigurationSets` | | [GetConfigurationSetAction](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetConfigurationSet.html) | Retrieve detailed sending, reputation, delivery, tracking, and suppression data for each configuration set. | Default | `ses:GetConfigurationSet` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **SES Configuration Set blueprint (click to expand)** Create in Port ``` { "identifier": "sesConfigurationSet", "title": "SES Configuration Set", "icon": "AWS", "schema": { "properties": { "sendingEnabled": { "type": "boolean", "title": "Sending Enabled", "description": "Indicates whether email sending is enabled for the configuration set" }, "reputationMetricsEnabled": { "type": "boolean", "title": "Reputation Metrics Enabled", "description": "Indicates whether reputation metrics tracking is enabled" }, "tlsPolicy": { "type": "string", "title": "TLS Policy", "description": "The TLS policy that applies to the configuration set (REQUIRE or OPTIONAL)" }, "trackingDomain": { "type": "string", "title": "Tracking Domain", "description": "The domain used to track open and click events" }, "suppressionList": { "type": "array", "title": "Suppression List", "description": "The reasons that email addresses are automatically added to the suppression list", "items": { "type": "string" } }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the configuration set", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **SES Configuration Set mapping configuration (click to expand)** ``` resources: - kind: AWS::SES::ConfigurationSet selector: query: 'true' port: entity: mappings: identifier: .Properties.ConfigurationSetName title: .Properties.ConfigurationSetName blueprint: '"sesConfigurationSet"' properties: sendingEnabled: .Properties.SendingOptions.SendingEnabled reputationMetricsEnabled: .Properties.ReputationOptions.ReputationMetricsEnabled tlsPolicy: .Properties.DeliveryOptions.TlsPolicy trackingDomain: .Properties.TrackingOptions.CustomRedirectDomain suppressionList: .Properties.SuppressionOptions.SuppressedReasons tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about SES configuration set properties, refer to the [AWS SES API documentation](https://docs.aws.amazon.com/ses/latest/APIReference-V2/API_GetConfigurationSet.html). --- # Amazon Simple Queue Service (SQS) ## AWS::SQS::Queue[​](#awssqsqueue "Direct link to AWS::SQS::Queue") The following example demonstrates how to ingest your AWS SQS queues to Port. #### SQS Queue supported actions[​](#sqs-queue-supported-actions "Direct link to SQS Queue supported actions") The table below summarizes the available actions for ingesting Amazon SQS Queue resources in Port: | Action | Description | Type | Required AWS Permission | | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -------- | ------------------------ | | [ListQueuesAction](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ListQueues.html) | Discover all SQS queues across your AWS account. | Default | `sqs:ListQueues` | | [GetQueueAttributesAction](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_GetQueueAttributes.html) | Retrieve detailed configuration and operational data for each queue. | Default | `sqs:GetQueueAttributes` | | [ListQueueTagsAction](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ListQueueTags.html) | Bring in custom tags assigned to your queues for advanced catalog filtering and grouping. | Optional | `sqs:ListQueueTags` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **SQS Queue blueprint (click to expand)** Create in Port ``` { "identifier": "sqsQueue", "title": "SQS Queue", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the SQS queue" }, "region": { "type": "string", "title": "Region", "description": "The AWS region where the SQS queue is located" }, "queueUrl": { "type": "string", "title": "Queue URL", "description": "The URL of the SQS queue" }, "approximateNumberOfMessages": { "type": "number", "title": "Approx Number of Messages", "description": "The approximate number of messages available for retrieval from the queue" }, "visibilityTimeout": { "type": "number", "title": "Visibility Timeout (seconds)", "description": "The visibility timeout for the queue in seconds" }, "messageRetentionPeriod": { "type": "number", "title": "Message Retention Period", "description": "The length of time, in seconds, for which Amazon SQS retains a message" }, "sqsManagedSseEnabled": { "type": "boolean", "title": "SQS Managed SSE Enabled", "description": "Whether SQS managed server-side encryption is enabled" }, "tags": { "type": "object", "title": "tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **SQS Queue mapping configuration (click to expand)** ``` - kind: AWS::SQS::Queue selector: query: 'true' includeActions: - ListQueueTagsAction port: entity: mappings: identifier: .Properties.QueueArn title: .Properties.QueueUrl | split("/") | .[-1] blueprint: '"sqsQueue"' properties: arn: .Properties.QueueArn region: .__ExtraContext.Region queueUrl: .Properties.QueueUrl approximateNumberOfMessages: .Properties.ApproximateNumberOfMessages visibilityTimeout: .Properties.VisibilityTimeout messageRetentionPeriod: .Properties.MessageRetentionPeriod sqsManagedSseEnabled: .Properties.SqsManagedSseEnabled tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about SQS queue properties, refer to the [AWS SQS API documentation](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_GetQueueAttributes.html). --- # Amazon Simple Storage Service (S3) ## AWS::S3::Bucket[​](#awss3bucket "Direct link to AWS::S3::Bucket") The following example demonstrates how to ingest your AWS S3 buckets to Port. #### S3 Bucket supported actions[​](#s3-bucket-supported-actions "Direct link to S3 Bucket supported actions") The table below summarizes the available actions for ingesting Amazon S3 Bucket resources in Port: | Action | Description | Type | Required AWS Permission | | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | -------- | ------------------------------- | | [ListBucketsAction](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html) | Discover all S3 buckets across your AWS account. | Default | `s3:ListAllMyBuckets` | | [GetBucketTaggingAction](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketTagging.html) | Bring in bucket tags for catalog filtering and grouping. | Default | `s3:GetBucketTagging` | | [GetBucketLocationAction](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html) | Retrieve the bucket's region. | Optional | `s3:GetBucketLocation` | | [GetBucketEncryptionAction](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketEncryption.html) | Retrieve server-side encryption configuration for the bucket. | Optional | `s3:GetBucketEncryption` | | [GetBucketPublicAccessBlockAction](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetPublicAccessBlock.html) | Retrieve public access block configuration. | Optional | `s3:GetBucketPublicAccessBlock` | | [GetBucketOwnershipControlsAction](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketOwnershipControls.html) | Retrieve bucket ownership controls. | Optional | `s3:GetBucketOwnershipControls` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **S3 Bucket blueprint (click to expand)** Create in Port ``` { "identifier": "s3Bucket", "description": "This blueprint represents an AWS S3 bucket in our software catalog", "title": "S3 bucket", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN" }, "creationDate": { "type": "string", "format": "date-time", "title": "Creation date" }, "tags": { "type": "array", "title": "Tags", "items": { "type": "object", "properties": { "Key": { "type": "string" }, "Value": { "type": "string" } } } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **S3 Bucket mapping configuration (click to expand)** ``` resources: - kind: AWS::S3::Bucket selector: query: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Properties.BucketName blueprint: '"s3Bucket"' properties: arn: .Properties.Arn creationDate: .Properties.CreationDate tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` For more details about S3 bucket properties, refer to the [AWS S3 API documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html). --- # AWS Account ## AWS::Account::Info[​](#awsaccountinfo "Direct link to AWS::Account::Info") The following example demonstrates how to ingest AWS account information using a custom resource designed for account resync without AWS Organizations permissions. Custom Resource for Account Resync Use `AWS::Account::Info` to resync AWS account data when you do not have organization-level permissions. If you do have AWS Organizations permissions and want to retrieve richer data (e.g., org account metadata, parents, and tags), see the AWS Organizations documentation under [AWS Organizations](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/resource-and-property-reference/aws-organizations/.md). You can use the following Port blueprint definitions and integration configuration: **AWS Account blueprint (click to expand)** Create in Port ``` { "identifier": "awsAccount", "title": "AWS Account", "icon": "AWS", "schema": { "properties": {}, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **AWS Account info mapping configuration (click to expand)** ``` resources: - kind: AWS::Account::Info selector: query: 'true' port: entity: mappings: identifier: .Properties.Id title: .Properties.Name blueprint: '"awsAccount"' ``` --- # AWS CodeBuild ## AWS::CodeBuild::Project[​](#awscodebuildproject "Direct link to AWS::CodeBuild::Project") The following example demonstrates how to ingest your AWS CodeBuild projects to Port. #### CodeBuild Project supported actions[​](#codebuild-project-supported-actions "Direct link to CodeBuild Project supported actions") The table below summarizes the available actions for ingesting AWS CodeBuild Project resources in Port: | Action | Description | Type | Required AWS Permission | | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------ | | [GetProjectDetailsAction](https://docs.aws.amazon.com/codebuild/latest/APIReference/API_BatchGetProjects.html) | Discover CodeBuild projects and retrieve detailed configuration data such as source, environment, and artifacts. | Default | `codebuild:ListProjects`, `codebuild:BatchGetProjects` | All properties available by default CodeBuild projects expose their key properties via the default GetProjectDetailsAction. You can use the following Port blueprint definitions and integration configuration: **CodeBuild Project blueprint (click to expand)** Create in Port ``` { "identifier": "codeBuildProject", "title": "CodeBuild Project", "icon": "AWS", "schema": { "properties": { "name": { "type": "string", "title": "Project Name", "description": "The name of the CodeBuild project" }, "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the CodeBuild project" }, "description": { "type": "string", "title": "Description", "description": "A description of the CodeBuild project" }, "serviceRole": { "type": "string", "title": "Service Role", "description": "The ARN of the AWS Identity and Access Management (IAM) service role" }, "timeoutInMinutes": { "type": "number", "title": "Timeout (Minutes)", "description": "How long, in minutes, from 5 to 480, for AWS CodeBuild to wait before timing out any related build" }, "queuedTimeoutInMinutes": { "type": "number", "title": "Queued Timeout (Minutes)", "description": "The number of minutes a build is allowed to be queued before timing out" }, "encryptionKey": { "type": "string", "title": "Encryption Key", "description": "The AWS Key Management Service (AWS KMS) customer master key (CMK) to be used for encrypting the build project artifacts" }, "environment": { "type": "object", "title": "Build Environment", "description": "Information about the build environment for the build project" }, "source": { "type": "object", "title": "Source", "description": "Information about the build input source code for the build project" }, "artifacts": { "type": "object", "title": "Artifacts", "description": "Information about the build output artifacts for the build project" }, "created": { "type": "string", "title": "Created", "description": "When the build project was created", "format": "date-time" }, "lastModified": { "type": "string", "title": "Last Modified", "description": "When the build project was last modified", "format": "date-time" }, "projectVisibility": { "type": "string", "title": "Project Visibility", "description": "The visibility setting for the project", "enum": [ "PRIVATE", "PUBLIC_READ" ] }, "concurrentBuildLimit": { "type": "number", "title": "Concurrent Build Limit", "description": "The maximum number of concurrent builds that are allowed for this project" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the CodeBuild project", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodeBuild Project mapping configuration (click to expand)** ``` resources: - kind: AWS::CodeBuild::Project selector: query: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Properties.Name blueprint: '"codeBuildProject"' properties: name: .Properties.Name arn: .Properties.Arn description: .Properties.Description serviceRole: .Properties.ServiceRole timeoutInMinutes: .Properties.TimeoutInMinutes queuedTimeoutInMinutes: .Properties.QueuedTimeoutInMinutes encryptionKey: .Properties.EncryptionKey environment: .Properties.Environment source: .Properties.Source artifacts: .Properties.Artifacts created: .Properties.Created lastModified: .Properties.LastModified projectVisibility: .Properties.ProjectVisibility concurrentBuildLimit: .Properties.ConcurrentBuildLimit tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` ## AWS::CodeBuild::BuildRun[​](#awscodebuildbuildrun "Direct link to AWS::CodeBuild::BuildRun") The following example demonstrates how to ingest your AWS CodeBuild build runs to Port. #### CodeBuild Build Run supported actions[​](#codebuild-build-run-supported-actions "Direct link to CodeBuild Build Run supported actions") The table below summarizes the available actions for ingesting AWS CodeBuild Build Run resources in Port: | Action | Description | Type | Required AWS Permission | | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ------- | -------------------------- | | [ListBuildsAction](https://docs.aws.amazon.com/codebuild/latest/APIReference/API_ListBuilds.html) | Discover build run IDs in the account. | Default | `codebuild:ListBuilds` | | [GetBuildDetailsAction](https://docs.aws.amazon.com/codebuild/latest/APIReference/API_BatchGetBuilds.html) | Retrieve detailed build run data such as status, phase, and timing. | Default | `codebuild:BatchGetBuilds` | All properties available by default CodeBuild build runs expose their key properties via the default ListBuildsAction and GetBuildDetailsAction. You can use the following Port blueprint definitions and integration configuration: **CodeBuild Build Run blueprint (click to expand)** Create in Port ``` { "identifier": "codeBuildBuildRun", "title": "CodeBuild Build Run", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the build run" }, "projectName": { "type": "string", "title": "Project Name", "description": "The name of the CodeBuild project" }, "buildNumber": { "type": "number", "title": "Build Number", "description": "The build number" }, "buildStatus": { "type": "string", "title": "Build Status", "description": "The current status of the build", "enum": [ "SUCCEEDED", "FAILED", "FAULT", "TIMED_OUT", "IN_PROGRESS", "STOPPED" ] }, "currentPhase": { "type": "string", "title": "Current Phase", "description": "The current phase of the build" }, "startTime": { "type": "string", "title": "Start Time", "description": "The time the build started", "format": "date-time" }, "endTime": { "type": "string", "title": "End Time", "description": "The time the build ended", "format": "date-time" }, "sourceVersion": { "type": "string", "title": "Source Version", "description": "The version of the source code" }, "buildComplete": { "type": "boolean", "title": "Build Complete", "description": "Whether the build is complete" }, "initiator": { "type": "string", "title": "Initiator", "description": "The entity that initiated the build" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodeBuild Build Run mapping configuration (click to expand)** ``` resources: - kind: AWS::CodeBuild::BuildRun selector: query: 'true' port: entity: mappings: identifier: .Properties.Id title: .Properties.ProjectName + " - Build " + (.Properties.BuildNumber | tostring) blueprint: '"codeBuildBuildRun"' properties: arn: .Properties.Arn projectName: .Properties.ProjectName buildNumber: .Properties.BuildNumber buildStatus: .Properties.BuildStatus currentPhase: .Properties.CurrentPhase startTime: .Properties.StartTime endTime: .Properties.EndTime sourceVersion: .Properties.SourceVersion buildComplete: .Properties.BuildComplete initiator: .Properties.Initiator relations: account: .__ExtraContext.AccountId ``` For more details about CodeBuild properties, refer to the [AWS CodeBuild API documentation](https://docs.aws.amazon.com/codebuild/latest/APIReference/Welcome.html). --- # AWS CodeDeploy ## AWS::CodeDeploy::Application[​](#awscodedeployapplication "Direct link to AWS::CodeDeploy::Application") The following example demonstrates how to ingest your AWS CodeDeploy applications to Port. #### CodeDeploy Application supported actions[​](#codedeploy-application-supported-actions "Direct link to CodeDeploy Application supported actions") The table below summarizes the available actions for ingesting AWS CodeDeploy Application resources in Port: | Action | Description | Type | Required AWS Permission | | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------- | ---------------------------------------------------------------- | | [GetCodeDeployApplicationDetailsAction](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_BatchGetApplications.html) | Discover CodeDeploy applications and retrieve configuration details. | Default | `codedeploy:ListApplications`, `codedeploy:BatchGetApplications` | | [GetCodeDeployApplicationTagsAction](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_ListTagsForResource.html) | Retrieve tags for each CodeDeploy application. | Default | `codedeploy:ListTagsForResource` | All properties available by default CodeDeploy applications expose their key properties, including tags, via the default actions. You can use the following Port blueprint definitions and integration configuration: **CodeDeploy Application blueprint (click to expand)** Create in Port ``` { "identifier": "codeDeployApplication", "title": "CodeDeploy Application", "icon": "AWS", "schema": { "properties": { "applicationName": { "type": "string", "title": "Application Name" }, "applicationId": { "type": "string", "title": "Application ID" }, "createTime": { "type": "string", "title": "Create Time", "format": "date-time" }, "linkedToGitHub": { "type": "boolean", "title": "Linked To GitHub" }, "gitHubAccountName": { "type": "string", "title": "GitHub Account Name" }, "computePlatform": { "type": "string", "title": "Compute Platform" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the Application", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodeDeploy Application mapping configuration (click to expand)** ``` resources: - kind: AWS::CodeDeploy::Application selector: query: 'true' port: entity: mappings: identifier: .Properties.ApplicationId title: .Properties.ApplicationName blueprint: '"codeDeployApplication"' properties: applicationName: .Properties.ApplicationName applicationId: .Properties.ApplicationId createTime: .Properties.CreateTime linkedToGitHub: .Properties.LinkedToGitHub gitHubAccountName: .Properties.GitHubAccountName computePlatform: .Properties.ComputePlatform tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` ## AWS::CodeDeploy::DeploymentGroup[​](#awscodedeploydeploymentgroup "Direct link to AWS::CodeDeploy::DeploymentGroup") The following example demonstrates how to ingest your AWS CodeDeploy deployment groups to Port. #### CodeDeploy Deployment Group supported actions[​](#codedeploy-deployment-group-supported-actions "Direct link to CodeDeploy Deployment Group supported actions") The table below summarizes the available actions for ingesting AWS CodeDeploy Deployment Group resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------- | | [GetDeploymentGroupDetailsAction](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_BatchGetDeploymentGroups.html) | Discover deployment groups and retrieve configuration details such as compute platform and deployment style. | Default | `codedeploy:ListApplications`, `codedeploy:ListDeploymentGroups`, `codedeploy:BatchGetDeploymentGroups` | | [GetDeploymentGroupTags](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_ListTagsForResource.html) | Retrieve tags for each deployment group. | Optional | `codedeploy:ListTagsForResource` | Optional properties and discovery Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. The integration lists CodeDeploy applications first, then fetches deployment groups for each application. You can use the following Port blueprint definitions and integration configuration: **CodeDeploy Deployment Group blueprint (click to expand)** Create in Port ``` { "identifier": "codedeployDeploymentGroup", "title": "CodeDeploy Deployment Group", "icon": "AWS", "schema": { "properties": { "applicationName": { "type": "string", "title": "Application Name", "description": "The CodeDeploy application name" }, "deploymentGroupId": { "type": "string", "title": "Deployment Group ID", "description": "The deployment group ID" }, "serviceRoleArn": { "type": "string", "title": "Service Role ARN", "description": "The ARN of the service role for the deployment group" }, "computePlatform": { "type": "string", "title": "Compute Platform", "description": "The compute platform for the deployment group", "enum": [ "Server", "Lambda", "ECS" ] }, "deploymentStyle": { "type": "object", "title": "Deployment Style", "description": "Information about the type of deployment" }, "autoScalingGroups": { "type": "array", "title": "Auto Scaling Groups", "description": "A list of associated Auto Scaling groups", "items": { "type": "object" } }, "loadBalancerInfo": { "type": "object", "title": "Load Balancer Info", "description": "Information about the load balancer used in the deployment" }, "outdatedInstancesStrategy": { "type": "string", "title": "Outdated Instances Strategy", "description": "Indicates what happens when new Amazon EC2 instances are launched", "enum": [ "UPDATE", "IGNORE" ] }, "tags": { "type": "array", "title": "Tags", "description": "The metadata tags associated with the deployment group", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodeDeploy Deployment Group mapping configuration (click to expand)** ``` resources: - kind: AWS::CodeDeploy::DeploymentGroup selector: query: 'true' port: entity: mappings: identifier: .Properties.DeploymentGroupId title: .Properties.ApplicationName + "/" + .Properties.DeploymentGroupName blueprint: '"codedeployDeploymentGroup"' properties: applicationName: .Properties.ApplicationName deploymentGroupId: .Properties.DeploymentGroupId serviceRoleArn: .Properties.ServiceRoleArn computePlatform: .Properties.ComputePlatform deploymentStyle: .Properties.DeploymentStyle autoScalingGroups: .Properties.AutoScalingGroups loadBalancerInfo: .Properties.LoadBalancerInfo outdatedInstancesStrategy: .Properties.OutdatedInstancesStrategy tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` ## AWS::CodeDeploy::Deployment[​](#awscodedeploydeployment "Direct link to AWS::CodeDeploy::Deployment") The following example demonstrates how to ingest your AWS CodeDeploy deployments to Port. #### CodeDeploy Deployment supported actions[​](#codedeploy-deployment-supported-actions "Direct link to CodeDeploy Deployment supported actions") The table below summarizes the available actions for ingesting AWS CodeDeploy Deployment resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------- | ------- | ---------------------------- | | [ListDeploymentsAction](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_ListDeployments.html) | Discover deployment IDs in the region. | Default | `codedeploy:ListDeployments` | | [GetDeploymentAction](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_GetDeployment.html) | Retrieve detailed deployment data such as status, creator, and timing. | Default | `codedeploy:GetDeployment` | All properties available by default CodeDeploy deployments expose their key properties via the default ListDeploymentsAction and GetDeploymentAction. You can use the following Port blueprint definitions and integration configuration: **CodeDeploy Deployment blueprint (click to expand)** Create in Port ``` { "identifier": "codeDeployDeployment", "title": "CodeDeploy Deployment", "icon": "AWS", "schema": { "properties": { "deploymentId": { "type": "string", "title": "Deployment ID", "description": "The unique ID of the deployment" }, "applicationName": { "type": "string", "title": "Application Name", "description": "The name of the application" }, "deploymentGroupName": { "type": "string", "title": "Deployment Group", "description": "The name of the deployment group" }, "status": { "type": "string", "title": "Status", "description": "The status of the deployment", "enum": [ "Created", "Queued", "InProgress", "Succeeded", "Failed", "Stopped", "Ready" ] }, "creator": { "type": "string", "title": "Creator", "description": "The user who created the deployment" }, "createTime": { "type": "string", "title": "Create Time", "description": "The time the deployment was created", "format": "date-time" }, "startTime": { "type": "string", "title": "Start Time", "description": "The time the deployment started", "format": "date-time" }, "completeTime": { "type": "string", "title": "Complete Time", "description": "The time the deployment completed", "format": "date-time" }, "description": { "type": "string", "title": "Description", "description": "A comment about the deployment" }, "deploymentStyle": { "type": "object", "title": "Deployment Style", "description": "Information about the type of deployment" }, "region": { "type": "string", "title": "Region", "description": "AWS region where the deployment is located" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodeDeploy Deployment mapping configuration (click to expand)** ``` resources: - kind: AWS::CodeDeploy::Deployment selector: query: 'true' port: entity: mappings: identifier: .Properties.DeploymentId title: .Properties.ApplicationName + "/" + .Properties.DeploymentGroupName + "/" + .Properties.DeploymentId blueprint: '"codeDeployDeployment"' properties: deploymentId: .Properties.DeploymentId applicationName: .Properties.ApplicationName deploymentGroupName: .Properties.DeploymentGroupName status: .Properties.Status creator: .Properties.Creator createTime: .Properties.CreateTime startTime: .Properties.StartTime completeTime: .Properties.CompleteTime description: .Properties.Description deploymentStyle: .Properties.DeploymentStyle region: .__ExtraContext.Region relations: account: .__ExtraContext.AccountId ``` ## AWS::CodeDeploy::DeploymentTarget[​](#awscodedeploydeploymenttarget "Direct link to AWS::CodeDeploy::DeploymentTarget") The following example demonstrates how to ingest your AWS CodeDeploy deployment targets to Port. #### CodeDeploy Deployment Target supported actions[​](#codedeploy-deployment-target-supported-actions "Direct link to CodeDeploy Deployment Target supported actions") The table below summarizes the available actions for ingesting AWS CodeDeploy Deployment Target resources in Port: | Action | Description | Type | Required AWS Permission | | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------- | | [GetDeploymentTargetDetailsAction](https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_BatchGetDeploymentTargets.html) | Retrieve deployment target details for each deployment, including target type and platform-specific data. | Default | `codedeploy:ListDeployments`, `codedeploy:ListDeploymentTargets`, `codedeploy:BatchGetDeploymentTargets` | Discovery flow The integration lists deployments first, then fetches targets for each deployment. Deployments that have not started are skipped. You can use the following Port blueprint definitions and integration configuration: **CodeDeploy Deployment Target blueprint (click to expand)** Create in Port ``` { "identifier": "codedeployDeploymentTarget", "title": "CodeDeploy Deployment Target", "icon": "AWS", "schema": { "properties": { "deploymentId": { "type": "string", "title": "Deployment ID", "description": "The unique ID of the deployment" }, "deploymentTargetType": { "type": "string", "title": "Deployment Target Type", "description": "The deployment type that is specific to the deployment's compute platform", "enum": [ "InstanceTarget", "LambdaTarget", "ECSTarget", "CloudFormationTarget" ] } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodeDeploy Deployment Target mapping configuration (click to expand)** ``` resources: - kind: AWS::CodeDeploy::DeploymentTarget selector: query: 'true' port: entity: mappings: identifier: .Properties.DeploymentId + "/" + .Properties.TargetId title: .Properties.DeploymentId + "/" + .Properties.TargetId blueprint: '"codedeployDeploymentTarget"' properties: deploymentId: .Properties.DeploymentId deploymentTargetType: .Properties.DeploymentTargetType relations: account: .__ExtraContext.AccountId ``` For more details about CodeDeploy properties, refer to the [AWS CodeDeploy API documentation](https://docs.aws.amazon.com/codedeploy/latest/APIReference/Welcome.html). --- # AWS CodePipeline ## AWS::CodePipeline::Pipeline[​](#awscodepipelinepipeline "Direct link to AWS::CodePipeline::Pipeline") The following example demonstrates how to ingest your AWS CodePipeline pipelines to Port. #### CodePipeline Pipeline supported actions[​](#codepipeline-pipeline-supported-actions "Direct link to CodePipeline Pipeline supported actions") The table below summarizes the available actions for ingesting AWS CodePipeline Pipeline resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | ------- | ---------------------------------- | | [ListPipelinesAction](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_ListPipelines.html) | Discover CodePipeline pipelines in the region. | Default | `codepipeline:ListPipelines` | | [GetPipelineDetailsAction](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_GetPipeline.html) | Retrieve detailed pipeline configuration such as stages, role ARN, and version. | Default | `codepipeline:GetPipeline` | | [GetPipelineTagsAction](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_ListTagsForResource.html) | Retrieve tags for each pipeline. | Default | `codepipeline:ListTagsForResource` | All properties available by default CodePipeline pipelines expose their key properties, including tags, via the default actions. You can use the following Port blueprint definitions and integration configuration: **CodePipeline Pipeline blueprint (click to expand)** Create in Port ``` { "identifier": "codePipeline", "title": "CodePipeline Pipeline", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN", "description": "The Amazon Resource Name (ARN) of the pipeline" }, "roleArn": { "type": "string", "title": "Role ARN", "description": "The service role ARN for the pipeline" }, "executionMode": { "type": "string", "title": "Execution Mode", "description": "The execution mode of the pipeline" }, "pipelineType": { "type": "string", "title": "Pipeline Type", "description": "The type of the pipeline" }, "version": { "type": "number", "title": "Version", "description": "The version number of the pipeline" }, "stages": { "type": "array", "title": "Stages", "description": "The stages of the pipeline", "items": { "type": "object" } }, "created": { "type": "string", "title": "Created", "description": "The date when the pipeline was created", "format": "date-time" }, "updated": { "type": "string", "title": "Updated", "description": "The date when the pipeline was last updated", "format": "date-time" }, "tags": { "type": "array", "title": "Tags", "description": "Tags associated with the pipeline", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodePipeline Pipeline mapping configuration (click to expand)** ``` resources: - kind: AWS::CodePipeline::Pipeline selector: query: 'true' port: entity: mappings: identifier: .Properties.PipelineArn title: .Properties.Name blueprint: '"codePipeline"' properties: arn: .Properties.PipelineArn roleArn: .Properties.RoleArn executionMode: .Properties.ExecutionMode pipelineType: .Properties.PipelineType version: .Properties.Version stages: .Properties.Stages created: .Properties.Created updated: .Properties.Updated tags: .Properties.Tags relations: account: .__ExtraContext.AccountId ``` ## AWS::CodePipeline::Stage[​](#awscodepipelinestage "Direct link to AWS::CodePipeline::Stage") The following example demonstrates how to ingest your AWS CodePipeline stages to Port. #### CodePipeline Stage supported actions[​](#codepipeline-stage-supported-actions "Direct link to CodePipeline Stage supported actions") The table below summarizes the available actions for ingesting AWS CodePipeline Stage resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------- | | [GetPipelineStagesAction](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_GetPipeline.html) | Discover pipeline stages by expanding each pipeline definition into individual stage records. | Default | `codepipeline:ListPipelines`, `codepipeline:GetPipeline` | Discovery flow The integration lists pipelines first, then expands each pipeline's `stages` array into separate stage entities with `pipelineName`, `pipelineArn`, and `order` metadata. You can use the following Port blueprint definitions and integration configuration: **CodePipeline Stage blueprint (click to expand)** Create in Port ``` { "identifier": "codePipelineStage", "title": "CodePipeline Stage", "icon": "AWS", "schema": { "properties": { "pipelineName": { "type": "string", "title": "Pipeline Name", "description": "The name of the CodePipeline pipeline this stage belongs to" }, "pipelineArn": { "type": "string", "title": "Pipeline ARN", "description": "The Amazon Resource Name (ARN) of the pipeline" }, "actions": { "type": "array", "title": "Actions", "description": "The list of actions that are part of this stage", "items": { "type": "object" } }, "name": { "type": "string", "title": "Name", "description": "The name of the stage in the pipeline" }, "order": { "type": "number", "title": "Order", "description": "The sequential order of the stage within the pipeline" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodePipeline Stage mapping configuration (click to expand)** ``` resources: - kind: AWS::CodePipeline::Stage selector: query: 'true' port: entity: mappings: identifier: (.Properties.PipelineArn + "/" + .Properties.Name) title: (.Properties.PipelineName + "/" + .Properties.Name) blueprint: '"codePipelineStage"' properties: pipelineName: .Properties.PipelineName pipelineArn: .Properties.PipelineArn name: .Properties.Name order: .Properties.Order actions: .Properties.Actions relations: account: .__ExtraContext.AccountId ``` ## AWS::CodePipeline::Action[​](#awscodepipelineaction "Direct link to AWS::CodePipeline::Action") The following example demonstrates how to ingest your AWS CodePipeline actions to Port. #### CodePipeline Action supported actions[​](#codepipeline-action-supported-actions "Direct link to CodePipeline Action supported actions") The table below summarizes the available actions for ingesting AWS CodePipeline Action resources in Port: | Action | Description | Type | Required AWS Permission | | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------- | -------------------------------------------------------- | | [GetPipelineActionsDetails](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_GetPipeline.html) | Discover pipeline actions by expanding each stage's `actions` array into individual action records. | Default | `codepipeline:ListPipelines`, `codepipeline:GetPipeline` | Discovery flow The integration lists pipelines first, then expands each stage's actions into separate action entities with `pipelineName`, `stageName`, and `pipelineArn` metadata. You can use the following Port blueprint definitions and integration configuration: **CodePipeline Action blueprint (click to expand)** Create in Port ``` { "identifier": "codePipelineAction", "title": "CodePipeline Action", "icon": "AWS", "schema": { "properties": { "name": { "type": "string", "title": "Name", "description": "The name of the action" }, "provider": { "type": "string", "title": "Provider", "description": "The action provider (e.g., S3, CodeBuild, CodeDeploy)" }, "category": { "type": "string", "title": "Category", "description": "The action category (Source, Build, Deploy, etc.)" }, "owner": { "type": "string", "title": "Owner", "description": "The action owner (AWS, ThirdParty, Custom)" }, "version": { "type": "string", "title": "Version", "description": "The action version" }, "runOrder": { "type": "number", "title": "Run Order", "description": "The order in which the action runs" }, "configuration": { "type": "object", "title": "Configuration", "description": "Action configuration parameters" }, "inputArtifacts": { "type": "array", "title": "Input Artifacts", "description": "Input artifacts for the action", "items": { "type": "object" } }, "outputArtifacts": { "type": "array", "title": "Output Artifacts", "description": "Output artifacts produced by the action", "items": { "type": "object" } }, "roleArn": { "type": "string", "title": "Role ARN", "description": "The ARN of the role used for the action" }, "region": { "type": "string", "title": "Region", "description": "The region where the action runs" }, "pipelineName": { "type": "string", "title": "Pipeline Name", "description": "The name of the pipeline containing this action" }, "stageName": { "type": "string", "title": "Stage Name", "description": "The name of the stage containing this action" }, "pipelineArn": { "type": "string", "title": "Pipeline ARN", "description": "The ARN of the pipeline" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodePipeline Action mapping configuration (click to expand)** ``` resources: - kind: AWS::CodePipeline::Action selector: query: 'true' port: entity: mappings: identifier: .Properties.PipelineArn + "/" + .Properties.StageName + "/" + .Properties.Name title: .Properties.PipelineName + "/" + .Properties.StageName + "/" + .Properties.Name blueprint: '"codePipelineAction"' properties: name: .Properties.Name provider: .Properties.ActionTypeId.Provider category: .Properties.ActionTypeId.Category owner: .Properties.ActionTypeId.Owner version: .Properties.ActionTypeId.Version runOrder: .Properties.RunOrder configuration: .Properties.Configuration inputArtifacts: .Properties.InputArtifacts outputArtifacts: .Properties.OutputArtifacts roleArn: .Properties.RoleArn region: .Properties.Region pipelineName: .Properties.PipelineName stageName: .Properties.StageName pipelineArn: .Properties.PipelineArn relations: account: .__ExtraContext.AccountId ``` ## AWS::CodePipeline::PipelineExecution[​](#awscodepipelinepipelineexecution "Direct link to AWS::CodePipeline::PipelineExecution") The following example demonstrates how to ingest your AWS CodePipeline pipeline executions to Port. #### CodePipeline Pipeline Execution supported actions[​](#codepipeline-pipeline-execution-supported-actions "Direct link to CodePipeline Pipeline Execution supported actions") The table below summarizes the available actions for ingesting AWS CodePipeline Pipeline Execution resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------- | | [ListPipelineExecutionsAction](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_ListPipelineExecutions.html) | Discover pipeline execution summaries for each pipeline. | Default | `codepipeline:ListPipelines`, `codepipeline:ListPipelineExecutions` | | [GetPipelineExecutionDetailsAction](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_GetPipelineExecution.html) | Retrieve detailed execution data such as status, trigger, and artifact revisions. | Default | `codepipeline:GetPipelineExecution` | Discovery flow The integration lists pipelines first, then fetches executions for each pipeline. You can use the following Port blueprint definitions and integration configuration: **CodePipeline Pipeline Execution blueprint (click to expand)** Create in Port ``` { "identifier": "codePipelinePipelineExecution", "title": "CodePipeline Pipeline Execution", "icon": "AWS", "schema": { "properties": { "pipelineName": { "type": "string", "title": "Pipeline Name", "description": "The name of the pipeline" }, "pipelineVersion": { "type": "number", "title": "Pipeline Version", "description": "The version of the pipeline definition" }, "pipelineExecutionId": { "type": "string", "title": "Execution ID", "description": "The unique identifier for the pipeline execution" }, "status": { "type": "string", "title": "Status", "description": "The status of the pipeline execution", "enum": [ "InProgress", "Stopped", "Stopping", "Succeeded", "Superseded", "Failed" ] }, "statusSummary": { "type": "string", "title": "Status Summary", "description": "A summary of the status of the pipeline execution" }, "executionMode": { "type": "string", "title": "Execution Mode", "description": "The execution mode for this pipeline execution", "enum": [ "QUEUED", "SUPERSEDED", "PARALLEL" ] }, "trigger": { "type": "object", "title": "Trigger", "description": "The trigger that caused the pipeline execution" }, "artifactRevisions": { "type": "array", "title": "Artifact Revisions", "description": "A list of source code revisions for the pipeline execution", "items": { "type": "object" } }, "variables": { "type": "array", "items": { "type": "object" }, "title": "Variables", "description": "Variables defined for the pipeline execution" }, "lastUpdateTime": { "type": "string", "format": "date-time", "title": "Last Update Time", "description": "The time when the pipeline execution was last updated", "dateFormat": "relative" }, "startTime": { "type": "string", "format": "date-time", "title": "StartTime", "description": "The time when the pipeline execution started", "dateFormat": "relative" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodePipeline Pipeline Execution mapping configuration (click to expand)** ``` resources: - kind: AWS::CodePipeline::PipelineExecution selector: query: 'true' port: entity: mappings: identifier: .Properties.PipelineExecutionId title: .Properties.PipelineName + "/" + .Properties.PipelineExecutionId blueprint: '"codePipelinePipelineExecution"' properties: pipelineName: .Properties.PipelineName pipelineVersion: .Properties.PipelineVersion pipelineExecutionId: .Properties.PipelineExecutionId status: .Properties.Status statusSummary: .Properties.StatusSummary executionMode: .Properties.ExecutionMode trigger: .Properties.Trigger artifactRevisions: .Properties.ArtifactRevisions variables: .Properties.Variables startTime: .Properties.StartTime lastUpdateTime: .Properties.LastUpdateTime relations: account: .__ExtraContext.AccountId ``` ## AWS::CodePipeline::ActionExecution[​](#awscodepipelineactionexecution "Direct link to AWS::CodePipeline::ActionExecution") The following example demonstrates how to ingest your AWS CodePipeline action executions to Port. #### CodePipeline Action Execution supported actions[​](#codepipeline-action-execution-supported-actions "Direct link to CodePipeline Action Execution supported actions") The table below summarizes the available actions for ingesting AWS CodePipeline Action Execution resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | ------- | ----------------------------------------------------------------- | | [ListActionExecutionsAction](https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_ListActionExecutions.html) | Discover action executions for each pipeline in the region. | Default | `codepipeline:ListPipelines`, `codepipeline:ListActionExecutions` | Discovery flow The integration lists pipelines first, then fetches action executions for each pipeline. You can use the following Port blueprint definitions and integration configuration: **CodePipeline Action Execution blueprint (click to expand)** Create in Port ``` { "identifier": "codePipelineActionExecution", "title": "CodePipeline Action Execution", "icon": "AWS", "schema": { "properties": { "actionExecutionId": { "type": "string", "title": "Action Execution ID", "description": "The unique ID of the action execution" }, "actionName": { "type": "string", "title": "Action Name", "description": "The name of the action" }, "stageName": { "type": "string", "title": "Stage Name", "description": "The name of the stage containing the action" }, "pipelineExecutionId": { "type": "string", "title": "Pipeline Execution ID", "description": "The pipeline execution ID associated with this action execution" }, "pipelineVersion": { "type": "number", "title": "Pipeline Version", "description": "The version of the pipeline when the action was executed" }, "status": { "type": "string", "title": "Status", "description": "The status of the action execution", "enum": [ "InProgress", "Abandoned", "Succeeded", "Failed" ] }, "startTime": { "type": "string", "title": "Start Time", "description": "The start time of the action execution", "format": "date-time" }, "lastUpdateTime": { "type": "string", "title": "Last Update Time", "description": "The last update time of the action execution", "format": "date-time" }, "input": { "type": "object", "title": "Input", "description": "Input details for the action execution" }, "output": { "type": "object", "title": "Output", "description": "Output details for the action execution" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **CodePipeline Action Execution mapping configuration (click to expand)** ``` resources: - kind: AWS::CodePipeline::ActionExecution selector: query: 'true' port: entity: mappings: identifier: .Properties.ActionExecutionId title: .Properties.ActionName + "/" + .Properties.ActionExecutionId blueprint: '"codePipelineActionExecution"' properties: actionExecutionId: .Properties.ActionExecutionId actionName: .Properties.ActionName stageName: .Properties.StageName pipelineExecutionId: .Properties.PipelineExecutionId pipelineVersion: .Properties.PipelineVersion status: .Properties.Status startTime: .Properties.StartTime lastUpdateTime: .Properties.LastUpdateTime input: .Properties.Input output: .Properties.Output relations: account: .__ExtraContext.AccountId ``` For more details about CodePipeline properties, refer to the [AWS CodePipeline API documentation](https://docs.aws.amazon.com/codepipeline/latest/APIReference/Welcome.html). --- # AWS Lambda ## AWS::Lambda::Function[​](#awslambdafunction "Direct link to AWS::Lambda::Function") The following example demonstrates how to ingest your AWS Lambda functions to Port. #### Lambda Function supported actions[​](#lambda-function-supported-actions "Direct link to Lambda Function supported actions") The table below summarizes the available actions for ingesting AWS Lambda Function resources in Port: | Action | Description | Type | Required AWS Permission | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -------- | ----------------------- | | [ListFunctionsAction](https://docs.aws.amazon.com/lambda/latest/api/API_ListFunctions.html) | Discover all Lambda functions across your AWS account. | Default | `lambda:ListFunctions` | | [ListTagsAction](https://docs.aws.amazon.com/lambda/latest/api/API_ListTags.html) | Retrieve tags for each function. | Optional | `lambda:ListTags` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **Lambda Function blueprint (click to expand)** Create in Port ``` { "identifier": "lambdaFunction", "title": "Lambda Function", "icon": "AWS", "schema": { "properties": { "FunctionName": { "type": "string", "title": "Function Name", "description": "The name of the Lambda function" }, "FunctionArn": { "type": "string", "title": "Function ARN", "description": "The Amazon Resource Name (ARN) of the Lambda function" }, "Runtime": { "type": "string", "title": "Runtime", "description": "The runtime environment for the Lambda function" }, "Handler": { "type": "string", "title": "Handler", "description": "The function entry point" }, "MemorySize": { "type": "number", "title": "Memory Size", "description": "The amount of memory available to the function in MB" }, "Timeout": { "type": "number", "title": "Timeout", "description": "The function execution time limit in seconds" }, "State": { "type": "string", "title": "State", "description": "The current state of the function" }, "LastModified": { "type": "string", "title": "Last Modified", "description": "The date and time the function was last modified" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": false, "many": false } } } ``` **Lambda Function mapping configuration (click to expand)** ``` resources: - kind: AWS::Lambda::Function selector: query: 'true' port: entity: mappings: identifier: .Properties.FunctionArn title: .Properties.FunctionName blueprint: '"lambdaFunction"' properties: FunctionName: .Properties.FunctionName FunctionArn: .Properties.FunctionArn Runtime: .Properties.Runtime Handler: .Properties.Handler MemorySize: .Properties.MemorySize Timeout: .Properties.Timeout State: .Properties.State LastModified: .Properties.LastModified relations: account: .__ExtraContext.AccountId ``` For more details about Lambda function properties, refer to the [AWS Lambda API documentation](https://docs.aws.amazon.com/lambda/latest/api/welcome.html). --- # AWS Organizations ## AWS::Organizations::Account[​](#awsorganizationsaccount "Direct link to AWS::Organizations::Account") The following example demonstrates how to ingest your AWS Organizations accounts to Port. #### Organizations Account supported actions[​](#organizations-account-supported-actions "Direct link to Organizations Account supported actions") The table below summarizes the available actions for ingesting AWS Organizations Account resources in Port: | Action | Description | Type | Required AWS Permission | | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | -------- | ----------------------------------- | | [ListAccountsAction](https://docs.aws.amazon.com/organizations/latest/APIReference/API_ListAccounts.html) | Discover organization accounts and retrieve account metadata. | Default | `organizations:ListAccounts` | | [ListTagsForResourceAction](https://docs.aws.amazon.com/organizations/latest/APIReference/API_ListTagsForResource.html) | Retrieve tags for the specified account. | Optional | `organizations:ListTagsForResource` | | [ListParentsAction](https://docs.aws.amazon.com/organizations/latest/APIReference/API_ListParents.html) | Retrieve parent information for the specified account. | Optional | `organizations:ListParents` | Optional Properties Note Properties of optional actions will not appear in the response unless you explicitly include the action that provides them in your configuration. You can use the following Port blueprint definitions and integration configuration: **Organizations Account blueprint (click to expand)** Create in Port ``` { "identifier": "awsOrganizationAccount", "description": "This blueprint represents an AWS Organizations Account in our software catalog", "title": "Organizations Account", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN" }, "email": { "type": "string", "title": "Email" }, "status": { "type": "string", "title": "Status" }, "joinedTimestamp": { "type": "string", "title": "Joined Timestamp" }, "joinedMethod": { "type": "string", "title": "Joined Method" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Organizations Account mapping configuration (click to expand)** ``` resources: - kind: AWS::Organizations::Account selector: query: 'true' port: entity: mappings: identifier: .Properties.Id title: .Properties.AccountName blueprint: '"awsOrganizationAccount"' properties: arn: .Properties.Arn email: .Properties.Email status: .Properties.Status joinedTimestamp: .Properties.JoinedTimestamp joinedMethod: .Properties.JoinedMethod ``` For more details about Organizations accounts, refer to the [AWS Organizations API documentation](https://docs.aws.amazon.com/organizations/latest/APIReference/Welcome.html). --- # AWS Legacy ![AWS icon](/img/guides/icons/AWS.svg)![AWS icon](/img/guides/icons/dark/AWS.svg) Loading version... Port's AWS Legacy integration allows you to import your AWS resources into Port, according to your configuration. After the initial import of data, the integration will also listen to live events from AWS to update data in Port in real time. Supported resources ![External link icon](/img/icons/external-link.svg)[AWS::Organizations::Account](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-organizations-account.html)[AWS::ImageBuilder::Image](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-imagebuilder-image.html)[AWS::ACM::Certificate](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-certificatemanager-certificate.html)[AWS::CloudFormation::Stack](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-cloudformation-stack.html)[AWS::ElastiCache::Cluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-elasticache-cachecluster.html)[AWS::ELBV2::LoadBalancer](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-elasticloadbalancingv2-loadbalancer.html)[AWS::SQS::Queue](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-sqs-queue.html)[AWS::ResourceGroups::Group](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-resourcegroups-group.html)[AWS::S3::Bucket](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-bucket.html)[AWS::EC2::Instance](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-instance.html)[AWS::ECS::Cluster](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html) ## Core support [Live eventsSee supported resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/live-events/.md) ## Common use cases[​](#common-use-cases "Direct link to Common use cases") Easily fill your software catalog with data directly from your AWS Organization, for example: * Map all the resources in your AWS Accounts, including **ECS Clusters**, **S3 Buckets**, **EC2 Instances** and other AWS objects. * Watch for AWS resources changes (create/update/delete) in real-time, and automatically apply the changes to your entities in Port. * Use relations to create complete, easily digestible views of your AWS infrastructure inside Port. ## How it works[​](#how-it-works "Direct link to How it works") Port's AWS integration can retrieve all the resources supported by the [Cloud Control API](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html), and export them to Port as entities of existing blueprints. The AWS integration allows you to perform extract, transform, load (ETL) on data from the Cloud Control API into the desired software catalog data model. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: 1Helm2Prerequisites3IAM User4Install Helm (scheduled) * ✧Deploy on Kubernetes using Helm * ✧Multiple authentication methods (IAM User, IRSA) * ✧Scheduled resyncs of resources from AWS to Port Terraform (real-time) * ✧Deploy using Infrastructure as Code * ✧Real-time updates via event-based triggers * ✧Reproducible and version-controlled deployments Docker (one-time) * ✧Run as a Docker container on any platform * ✧One-time or on-demand execution * ✧Minimal setup requirements Prerequisites Additional setup guides * **[Multiple account support](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/multi_account/.md)** : enable the integration to collect data from multiple AWS accounts. * **[Live events](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/live-events/.md)** : set up real-time event processing after completing the installation. ### Recommended resource sizes To ensure optimal performance and avoid out-of-memory (OOM) errors, we recommend the following resources for this integration: * **CPU Limit**: `800m` * **CPU Request**: `200m` * **Memory Limit**: `2Gi` * **Memory Request**: `2Gi` Set resource values using the commands below: ``` helm install my-integration port-labs/port-ocean \ # ... other parameters --set ocean.resources.limits.cpu=800m \ --set ocean.resources.limits.memory=2Gi \ --set ocean.resources.requests.cpu=200m \ --set ocean.resources.requests.memory=2Gi ``` ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration you get after installing the AWS Legacy integration. **Default mapping configuration (click to expand)** ``` resources: - kind: AWS::Organizations::Account selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"awsAccount"' properties: arn: .Arn email: .Email status: .Status joined_method: .JoinedMethod joined_timestamp: .JoinedTimestamp | sub(" "; "T") - kind: AWS::S3::Bucket selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .Properties.RegionalDomainName | capture(".*\\.(?[^\\.]+)\\.amazonaws\\.com") | .region tags: .Properties.Tags arn: .Properties.Arn link: .Properties | select(.Arn != null) | "https://console.aws.amazon.com/go/view?arn=" + .Arn relations: account: .__AccountId - kind: AWS::EC2::Instance selector: query: 'true' port: entity: mappings: identifier: .Identifier title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .__Region tags: .Properties.Tags arn: .Properties.Arn link: .Properties | select(.Arn != null) | "https://console.aws.amazon.com/go/view?arn=" + .Arn relations: account: .__AccountId - kind: AWS::ECS::Cluster selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .__Region tags: .Properties.Tags arn: .Properties.Arn link: .Properties | select(.Arn != null) | "https://console.aws.amazon.com/go/view?arn=" + .Arn relations: account: .__AccountId ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view the mapping and blueprint definition for each resource type. For additional resources and advanced configurations, see the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/examples/.md) page. AWS AccountS3 BucketEC2 InstanceECS Cluster BlueprintJSON Create in Port ``` { "identifier": "awsAccount", "title": "AWS Account", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "ARN" }, "email": { "type": "string", "title": "Email" }, "status": { "type": "string", "title": "Status", "enum": ["ACTIVE", "SUSPENDED", "PENDING_CLOSURE"], "enumColors": { "ACTIVE": "green", "SUSPENDED": "red", "PENDING_CLOSURE": "yellow" } }, "joined_method": { "type": "string", "title": "Joined Method", "enum": ["INVITED", "CREATED"] }, "joined_timestamp": { "type": "string", "title": "Joined Timestamp", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: AWS::Organizations::Account selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"awsAccount"' properties: arn: .Arn email: .Email status: .Status joined_method: .JoinedMethod joined_timestamp: .JoinedTimestamp | sub(" "; "T") ``` BlueprintJSON Create in Port ``` { "identifier": "cloudResource", "title": "Cloud Resource", "icon": "AWS", "schema": { "properties": { "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" }, "link": { "type": "string", "title": "Link", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "target": "awsAccount", "title": "Account", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::S3::Bucket selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .Properties.RegionalDomainName | capture(".*\\.(?[^\\.]+)\\.amazonaws\\.com") | .region tags: .Properties.Tags arn: .Properties.Arn link: .Properties | select(.Arn != null) | "https://console.aws.amazon.com/go/view?arn=" + .Arn relations: account: .__AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "cloudResource", "title": "Cloud Resource", "icon": "AWS", "schema": { "properties": { "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" }, "link": { "type": "string", "title": "Link", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "target": "awsAccount", "title": "Account", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::EC2::Instance selector: query: 'true' port: entity: mappings: identifier: .Identifier title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .__Region tags: .Properties.Tags arn: .Properties.Arn link: .Properties | select(.Arn != null) | "https://console.aws.amazon.com/go/view?arn=" + .Arn relations: account: .__AccountId ``` BlueprintJSON Create in Port ``` { "identifier": "cloudResource", "title": "Cloud Resource", "icon": "AWS", "schema": { "properties": { "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" }, "link": { "type": "string", "title": "Link", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "target": "awsAccount", "title": "Account", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: AWS::ECS::Cluster selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Properties.Arn title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .__Region tags: .Properties.Tags arn: .Properties.Arn link: .Properties | select(.Arn != null) | "https://console.aws.amazon.com/go/view?arn=" + .Arn relations: account: .__AccountId ``` ## Further examples[​](#further-examples "Direct link to Further examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/examples/.md) page for practical configurations and their corresponding blueprint definitions. --- # AWS exporter Deprecated This exporter is deprecated, Port will discontinue support for organizations using it by Q2 2025. To integrate Port with AWS, use the [Ocean AWS integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md). Port's AWS integration provides the ability to export your AWS resources to Port, according to your configuration. You can define the integration to run both on schedule and on events. The integration supports real-time event processing, this allows for an accurate **real-time** representation of your AWS infrastructure inside Port. View the source code Port's AWS exporter is open source, view the source code [**here**](https://github.com/port-labs/port-aws-exporter). ## Common use cases[​](#common-use-cases "Direct link to Common use cases") Our AWS exporter allows you to easily enrich your software catalog with data from your AWS accounts, for instance: * Map resources in your accounts, including **S3 buckets**, **lambda functions**, **SQS queues**, **RDS DB instances**, **ECS services** and many other resource types. * Use relations to create a complete, easily digestible map of your AWS accounts inside Port. ## How it works[​](#how-it-works "Direct link to How it works") Port's AWS exporter can retrieve all the resources supported by the [AWS Cloud Control API](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html). The open source AWS exporter allows you to perform extract, transform, load (ETL) on data from AWS into the desired software catalog data model. The exporter is deployed using an [AWS serverless application](https://aws.amazon.com/serverless/sam/) that is installed on the account. [The serverless application](#exporter-aws-serverless-application) requires a [JSON configuration file](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#exporter-configjson-file) to describe the ETL process to load data into the developer portal, and an [IAM policy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#iam-policy) with the necessary permissions to list and read the configured resources. The exporter makes use of [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the AWS objects. ### Exporter `config.json` file[​](#exporter-configjson-file "Direct link to exporter-configjson-file") The `config.json` file is how you specify the exact resources you want to export from your AWS account, and also which entities and which properties in Port, you want to fill in with data. Here is an example snippet of the `config.json` file which demonstrates the ETL process for getting `lambda functions` data from the account into the software catalog: **config.json example (click to expand)** ``` { "resources": [ { "kind": "AWS::Lambda::Function", "selector": { "aws": { "regions": ["us-east-1", "us-west-1"] } }, "port": { "entity": { "mappings": [ { "identifier": ".FunctionName", "title": ".FunctionName", "blueprint": "function", "properties": { "memory": ".MemorySize", "timeout": ".Timeout", "runtime": ".Runtime" } } ] } } } ] } ``` #### Structure[​](#structure "Direct link to Structure") * The root key of the `config.json` file is the `resources` key. * The `kind` key is a specifier for a resource type from the AWS Cloud Control API following the `service-provider::service-name::data-type-name` format: ``` "resources": [ { "kind": "AWS::Lambda::Function", ... ``` List supported resource types To generate a list of supported resource types using AWS CLI, combine the results of the following commands: ``` aws cloudformation list-types --type RESOURCE --visibility PUBLIC --provisioning-type FULLY_MUTABLE aws cloudformation list-types --type RESOURCE --visibility PUBLIC --provisioning-type IMMUTABLE aws cloudformation list-types --type RESOURCE --visibility PRIVATE --provisioning-type FULLY_MUTABLE aws cloudformation list-types --type RESOURCE --visibility PRIVATE --provisioning-type IMMUTABLE ``` To determine if a specific `` is supported, use this command (JQ required, output is `true` or `false`): ``` aws cloudformation describe-type --type RESOURCE --type-name --query "ProvisioningType" | jq -c '. == "FULLY_MUTABLE" or . == "IMMUTABLE"' ``` For more information, read [**here**](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/resource-types.html#resource-types-determine-support). * The `selector` key lets you filter exactly which objects from the specified `kind` will be ingested to the software catalog. * The `query` key in the `selector` is a JQ boolean query. If evaluated to false for an object, the object will not be synced. * The `aws` key in the `selector` lets you specify a list of `regions` to sync from. ``` "resources": [ { "kind": "AWS::Lambda::Function", "selector": { "query": "true", "aws": { "regions": [ "us-east-1", "us-west-1" ] } }, "port": ... ``` Multi-region support You can choose to deploy the exporter per region, or in one region of your choice. The `regions` configuration key might be useful if you want to install the exporter in one region, but to export data from multiple regions. Some example use cases: * To sync all objects from the specified `kind` in the default region, do not specify a `selector`. * To sync all lambdas that are not owned by AWS Amplify service: ``` query: .FunctionName | startswith("amplify") | not ``` Special resources For some resources you have to provide additional information. For example, in order to sync all `AWS ELB Listeners` of a specific load balancer, use the `regions_config` and `resources_models` keys: ``` "selector": { "aws": { "regions": [ "eu-west-1" ], "regions_config": { "eu-west-1": { "resources_models": [ "{\"LoadBalancerArn\": \"\"}" ] } } } } ``` The table of the special resources with the required properties can be found [here](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/resource-operations-list.html#resource-operations-list-containers). * The `port`, `entity` and the `mappings` keys open the section used to map the AWS resource fields to Port entities, the `mappings` key is an array where each object matches the structure of an [entity](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#entity-json-structure). * Each mapping value is a JQ query, except for `blueprint` which has to be a static string. * The `itemsToParse` key in a mapping makes it possible to create multiple entities from a single AWS resource. * Any JQ expression can be used here, as long as it evaluates to an array of items. * `item` will be added to the JQ context as a key containing a reference to items in the array specified in `itemsToParse`. For an array of objects, keys from an object can be accessed using the `.item.KEY_NAME` syntax. ``` "resources": [ { "kind": "AWS::Lambda::Function", "selector": { "aws": { "regions": [ "us-east-1", "us-west-1" ] } }, "port": { "entity": { "mappings": [ { "identifier": ".FunctionName", "title": ".FunctionName", "blueprint": "function", "properties": { "memory": ".MemorySize", "timeout": ".Timeout", "runtime": ".Runtime" } }, { "itemsToParse": ".Layers", "identifier": ".item", "title": ".item", "blueprint": "functionLayer", "properties": {} } ] } } } ... ``` Important * **The order of the resources matters** when you have relations between resources. The AWS exporter will sync the resources in the same order as they appear in the `config.json`, so make sure to sort the resources by a logical order. For example, if you have a relation from SNS Topic to lambda function, put the Lambda function configuration first. * By its nature, the AWS exporter will keep the values of unmapped properties untouched. Go [here](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-update#usage) for further explanation about different entity creation strategies. To view a resource type schema and use it to compose a mapping, use [this](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) reference. Note that not all of the resource types listed in the reference are available for use with the Cloud Control API. A method to determine if a resource type is available is discussed [here](#structure). For additional options and information, read [here](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/resource-types.html#resource-types-schemas). #### Changing the configuration[​](#changing-the-configuration "Direct link to Changing the configuration") By default, the exporter saves a preconfigured `config.json` file to an [AWS S3 bucket](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingBucket.html) upon installation. The S3 bucket is named `port-aws-exporter--`, and is created by the exporter. To make a change to the configuration (e.g. change the mapping of a certain property): 1. Create a copy of the `config.json` file from the bucket, make the desired changes, and save it locally. 2. Use the AWS CLI to upload the file to the bucket, replacing the existing file. Replace `` and `` with your values, then run the following command: ``` aws s3api put-object --bucket "" --key "config.json" --body "" ``` ### IAM policy[​](#iam-policy "Direct link to IAM policy") The AWS exporter uses an [AWS IAM policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html) which specifies the permissions to `list` and `read` the AWS resources you want to export (the ones you configured in the [`config.json`](#exporter-configjson-file)). For example, in order to export `lambda functions`, you need to create a policy with the following definition: ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "lambda:GetFunction", "lambda:GetFunctionCodeSigningConfig", "lambda:ListFunctions" ], "Resource": "*" } ] } ``` Find resource actions for policy In order to find the resource actions that should be added to the policy, you should view the resource type schema, and locate the permissions for the **read** and **list** `handlers`. Use the following command to get the resource action for a specific `` (AWS CLI and JQ required): ``` aws cloudformation describe-type --type RESOURCE --type-name --query "Schema" | jq -c 'fromjson | .handlers | with_entries(select([.key] | inside(["list", "read"]))) | map(.permissions) | flatten' ``` More details can be found [here](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/resource-types.html#resource-types-schemas). ### Port credentials secret[​](#port-credentials-secret "Direct link to Port credentials secret") In order to manage entities in Port, the exporter needs access to your Port credentials, these are provided to the exporter via an [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html) secret. The secret value should be in the following format: ``` { "id": "", "clientSecret": "" } ``` Similarly to the bucket, you can create and manage your own secret, or delegate it to the exporter. Either way, you need to add Port's credentials to the secret on your own, when the secret is ready. ### Exporter AWS serverless application[​](#exporter-aws-serverless-application "Direct link to Exporter AWS serverless application") The `Exporter AWS serverless application` is how you install the exporter's [CloudFormation stack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacks.html). The stack consists of several components: * [S3 bucket](#changing-the-configuration) — where the [`config.json`](#exporter-configjson-file) should be saved. * [ASM secret](#port-credentials-secret) — where you should save your Port credentials (client id and secret), to allow the exporter to interact with Port's API. * [Lambda function](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-concepts.html#gettingstarted-concepts-function) — a resource that you can invoke to run the exporter code. The [IAM policy](#iam-policy) is attached to the execution role of the Lambda function. * [SQS queue](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html) — a queue of events, to be consumed by the exporter. Read [here](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/event-based-updates/.md) to learn how to use the exporter to consume and act on live events from different AWS services. * [EventsBridge scheduled rule](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html) — a rule to run the exporter on a schedule. ## Getting started[​](#getting-started "Direct link to Getting started") Continue to the [installation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/installation/.md) section to setup the AWS exporter in your Port environment. --- # Event-based updates Our AWS integration allows you to trigger a sync of your AWS resources with Port based on real-time events (in addition to the standard scheduled sync). As a result, your software catalog will be updated shortly after a resource is created, updated or deleted. ## How it works[​](#how-it-works "Direct link to How it works") Many AWS services emit events to [AWS EventBridge](https://aws.amazon.com/eventbridge/) service, to the account’s default event bus. Furthermore, the [AWS CloudTrail](https://aws.amazon.com/cloudtrail/) service automatically emits events for API calls from most AWS services. A user can create an [AWS EventBridge rule](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rules.html) to consume and transform particular events, and send them to a target, such as an [AWS SQS Queue](https://aws.amazon.com/sqs/). [The AWS exporter application](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#exporter-aws-serverless-application) creates an SQS Queue as part of its installation process, and configures it as an event source for the exporter's lambda. This allows you to set up an event rule, that consumes any events from the bus and sends them to the AWS exporter's queue. Each event will have to be transformed as part of the event rule, before reaching the queue, to suit the exporter's lambda expected event structure. The transformation goal is to map an event to a single AWS resource, that the exporter can handle. The events in the queue are consumed automatically by the exporter's lambda, that will sync the updated state of the relevant AWS resources with Port. ### Input structure for a single event[​](#input-structure-for-a-single-event "Direct link to Input structure for a single event") The AWS exporter's lambda accepts JSON events with the following structure: * `resource_type` - a hardcoded string representing the AWS resource type that is configured in the [`config.json`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#exporter-configjson-file) of the AWS Exporter. * `region` - a JQ query to get the region from the event, usually the value will be `"\"\""`. * `action` (Optional, defaults to `upsert`) - a JQ query to define the desired action: `upsert` or `delete` the Port entity of the matching resource. Will usually be based on the event name, like in the example below. * `identifier` - a JQ query to calculate the resource identifier. In case the action is `upsert`, it should be the AWS Cloud Control API resource identifier. Otherwise, for `delete` actions, it should be the Port entity identifier. It is recommended to use a Port entity identifier that is identical to the Cloud Control API resource identifier (when applicable), this will make it possible to use the same event rule both for upsert and delete events (while also saving the need for complex JQ filtering patterns). An example for such a JSON event: ``` { "requestFunctionName": "", "responseFunctionName": "", "eventName": "", "resource_type": "AWS::Lambda::Function", "region": "\"\"", "action": "if .eventName | startswith(\"Delete\") then \"delete\" else \"upsert\" end", "identifier": "if .responseFunctionName != \"\" then .responseFunctionName else .requestFunctionName end" } ``` Find the identifier formula To get the identifier formula of a specific resource type, like `AWS::Lambda::Function`, you can use the following command: ``` aws cloudformation describe-type --type RESOURCE --type-name AWS::Lambda::Function --query "Schema" | jq 'fromjson | .primaryIdentifier | join("|")' # Result "/properties/FunctionName" ``` Here you can see that the identifier of a lambda function is the function name. Another example, for the `AWS::ECS::Service` resource type: ``` aws cloudformation describe-type --type RESOURCE --type-name AWS::ECS::Service --query "Schema" | jq 'fromjson | .primaryIdentifier | join("|")' # Result "/properties/ServiceArn|/properties/Cluster" ``` Here you have two properties in the formula: `/properties/ServiceArn` and `/properties/Cluster`. When there are multiple properties, the values in the identifier are separated by `|`. For more information, read [**here**](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/resource-identifier.html). ### Event rule[​](#event-rule "Direct link to Event rule") #### Definition[​](#definition "Direct link to Definition") The event rule is how you specify the exact events you want to consume, and define the target to send the modified event to (including the transformation to the input). Before diving deep into the [event rule properties](#properties), here is a complete example of a `CloudFormation YAML template` to manage an event rule, for the AWS Exporter: ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: '^[a-zA-Z][-a-zA-Z0-9]*$' Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.lambda detail-type: - AWS API Call via CloudTrail detail: eventSource: - lambda.amazonaws.com eventName: - prefix: UpdateFunctionConfiguration - prefix: CreateFunction - prefix: DeleteFunction Name: port-aws-exporter-sync-lambda-trails State: ENABLED Targets: - Id: 'PortAWSExporterEventsQueue' Arn: { 'Fn::ImportValue': { 'Fn::Sub': '${PortAWSExporterStackName}-EventsQueueARN' }, } InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestFunctionName: $.detail.requestParameters.functionName responseFunctionName: $.detail.responseElements.functionName InputTemplate: |- { "resource_type": "AWS::Lambda::Function", "requestFunctionName": "", "responseFunctionName": "", "eventName": "", "region": "\"\"", "identifier": "if .responseFunctionName != \"\" then .responseFunctionName else .requestFunctionName end", "action": "if .eventName | startswith(\"Delete\") then \"delete\" else \"upsert\" end" } ``` About this example This example of CloudFormation stack can be used to act on changes of lambda functions. Moreover, it includes a parameter called `PortAWSExporterStackName`, that refers to the main exporter's stack name. That way, we can send the events to the existing exporter's events queue, to sync relevant AWS resources. #### Properties[​](#properties "Direct link to Properties") Let's review the properties of the event rule resource from above. * The `EventBusName` property value is `default`, as this is the bus that gets events from various AWS services automatically: ``` Properties: EventBusName: default ... ``` * The `EventPattern` property defines which events to consume, including which event sources to process, which filters to apply based on the event structure. Here, we define the event pattern to consume Cloudtrail API calls, sourced from the lambda service; On top of that, we filter only the `UpdateFunctionConfiguration`, `CreateFunction` and `DeleteFunction` events: ``` Properties: EventBusName: default EventPattern: source: - aws.lambda detail-type: - AWS API Call via CloudTrail detail: eventSource: - lambda.amazonaws.com eventName: - prefix: UpdateFunctionConfiguration - prefix: CreateFunction - prefix: DeleteFunction ``` Compose an event pattern For composing an event pattern, learn more about events from AWS services, including different sources and event names [here](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-service-event.html). For supported services in CloudTrail, click [here](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-aws-service-specific-topics.html). * The `Targets` property defines the targets of the produced events. For the AWS exporter, we want to configure its own event queue as the target: ``` Properties: ... Targets: - Id: "PortAWSExporterEventsQueue" Arn: { "Fn::ImportValue" : {"Fn::Sub": "${PortAWSExporterStackName}-EventsQueueARN" } } ... ``` * The `InputTransformer` key in the `Targets` property defines the transformation that will be applied to the event, before sending it to the target queue. ``` Properties: ... Targets: - Id: "PortAWSExporterEventsQueue" Arn: { "Fn::ImportValue" : {"Fn::Sub": "${PortAWSExporterStackName}-EventsQueueARN" } } InputTransformer: ... ``` * The `InputPathsMap` key in `InputTransformer` defines which fields to extract from the event. It makes use of [`JSONPath`](https://goessner.net/articles/JsonPath/index.html) syntax to configure the extraction: ``` Properties: ... Targets: - Id: "PortAWSExporterEventsQueue" Arn: { "Fn::ImportValue" : {"Fn::Sub": "${PortAWSExporterStackName}-EventsQueueARN" } } InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestFunctionName: $.detail.requestParameters.functionName responseFunctionName: $.detail.responseElements.functionName ... ``` For instance, the event sent to the EventBridge bus for the `CreateFunction` lambda API call has the following structure: ``` { ... "detail": { ... "eventSource": "lambda.amazonaws.com", "eventName": "CreateFunction20150331", "awsRegion": "eu-west-1", ... "requestParameters": { "functionName": "test-function", ... }, "responseElements": { "functionName": "test-function", ... }, ... } } ``` The `InputPathsMap` in the example below will extract only the `awsRegion`, `eventName`, and `functionName` from the API request and response. * The `InputTemplate` key in `InputTransformer` defines a template of the final input that will be sent to the target queue, in the format the exporter's lambda [expects](#input-structure-for-a-single-event). The template is rendered using the values from the `InputPathsMap`: ``` Properties: ... Targets: - Id: "PortAWSExporterEventsQueue" Arn: { "Fn::ImportValue" : {"Fn::Sub": "${PortAWSExporterStackName}-EventsQueueARN" } } InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestFunctionName: $.detail.requestParameters.functionName responseFunctionName: $.detail.responseElements.functionName InputTemplate: |- { "resource_type": "AWS::Lambda::Function", "requestFunctionName": "", "responseFunctionName": "", "eventName": "", "region": "\"\"", "identifier": "if .responseFunctionName != \"\" then .responseFunctionName else .requestFunctionName end", "action": "if .eventName | startswith(\"Delete\") then \"delete\" else \"upsert\" end" } ``` Here, the `InputTemplate` takes the information extracted in the `InputPathsMap`, and constructs a message that will be sent to the SQS queue and consumed by the exporter’s lambda. The message includes: * The hardcoded resource type (`AWS::Lambda::Function`). * The `awsRegion` from the event. * A JQ query to figure out the action (`upsert` or `delete` the Port entity), based on the `eventName`. * A JQ query to calculate the identifier, based on the function name from the API response or request. Learn more To read more about input transformer in EventBridge, click [here](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-transform-target-input.html). ## Next steps[​](#next-steps "Direct link to Next steps") ### Examples[​](#examples "Direct link to Examples") * Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/examples/.md) page for practical configurations and their corresponding blueprint definitions. * Refer to the Event Bridge rule [Terraform example](https://github.com/port-labs/terraform-aws-port-exporter/tree/main/examples/terraform_deploy_eventbridge_rule). --- # Examples Use the index on the right side of the page to find an example for the resource/s you’re looking to export. ## ECS clusters and ECS services[​](#ecs-clusters-and-ecs-services "Direct link to ECS clusters and ECS services") In this step-by-step example, you will export your `ECS clusters` and `ECS services` to Port. 1. Create the following Port blueprint: * **ECS Cluster** - will represent ECS clusters from the AWS account. * **ECS Service** - will represent ECS services from the AWS account. You may use the following definitions: **ECS Cluster blueprint (click to expand)** Create in Port ``` { "identifier": "ecsCluster", "description": "This blueprint represents an AWS ECS Cluster in our software catalog", "title": "ECS Cluster", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "capacityProviders": { "type": "array", "title": "Capacity Providers" }, "defaultCapacityProviderStrategy": { "type": "array", "title": "Default Capacity Provider Strategy" }, "clusterSettings": { "type": "array", "title": "Cluster Settings" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **ECS Service blueprint (click to expand)** Create in Port ``` { "identifier": "ecsService", "description": "This blueprint represents an AWS ECS Service in our software catalog", "title": "ECS Service", "icon": "Service", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "desiredCount": { "type": "number", "title": "Desired Count" }, "taskDefinition": { "type": "string", "title": "Task Definition" }, "launchType": { "type": "string", "enum": ["EC2", "FARGATE", "EXTERNAL"], "title": "Launch Type" }, "schedulingStrategy": { "type": "string", "enum": ["REPLICA", "DAEMON"], "title": "Scheduling Strategy" }, "loadBalancers": { "type": "array", "title": "Load Balancers" }, "securityGroups": { "type": "array", "title": "Security Groups" }, "subnets": { "type": "array", "title": "Subnets" }, "iamRole": { "type": "string", "format": "url", "title": "IAM Role", "icon": "Unlock" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "ecsCluster": { "target": "ecsCluster", "many": false, "required": false } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::ECS::Cluster", "port": { "entity": { "mappings": [ { "identifier": ".ClusterName", "title": ".ClusterName", "blueprint": "ecsCluster", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "capacityProviders": ".CapacityProviders", "defaultCapacityProviderStrategy": ".DefaultCapacityProviderStrategy", "clusterSettings": ".ClusterSettings", "tags": ".Tags", "arn": ".Arn" } } ] } } }, { "kind": "AWS::ECS::Service", "port": { "entity": { "mappings": [ { "identifier": ".ServiceName", "title": ".ServiceName", "blueprint": "ecsService", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .ServiceArn", "desiredCount": ".DesiredCount", "launchType": ".LaunchType", "cluster": ".Cluster | split(\"/\")[-1]", "schedulingStrategy": ".SchedulingStrategy", "loadBalancers": ".LoadBalancers", "securityGroups": ".NetworkConfiguration.AwsvpcConfiguration.SecurityGroups", "subnets": ".NetworkConfiguration.AwsvpcConfiguration.Subnets", "taskDefinition": ".TaskDefinition | split(\"/\")[-1]", "iamRole": ".Role | if . == null then null else \"https://console.aws.amazon.com/go/view?arn=\" + . end", "arn": ".ServiceArn" }, "relations": { "ecsCluster": ".Cluster | split(\"/\")[-1]" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ecs:DescribeClusters", "ecs:ListClusters", "ecs:DescribeServices", "ecs:ListServices" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in ECS clusters and ECS services. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: 2010-09-09 Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.ecs detail-type: - AWS API Call via CloudTrail detail: eventSource: - ecs.amazonaws.com eventName: - prefix: CreateCluster - prefix: DeleteCluster - prefix: UpdateCluster - prefix: UpdateClusterSettings - prefix: PutClusterCapacityProviders - prefix: TagResource - prefix: UntagResource Name: port-aws-exporter-sync-ecs-cluster-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion clusterName: $.detail.responseElements.cluster.clusterName eventName: $.detail.eventName resourceArn: $.detail.requestParameters.resourceArn InputTemplate: |- { "resource_type": "AWS::ECS::Cluster", "region": "\"\"", "identifier": "\"\" | if test(\"TagResource|UntagResource[^a-zA-Z]*$\") then \"\" | split(\"/\")[-1] else \"\" end", "action": "if \"\" | test(\"DeleteCluster[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } EventRule1: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AWS API Call via CloudTrail source: - aws.ecs detail: eventSource: - ecs.amazonaws.com eventName: - prefix: CreateService - prefix: UpdateService - prefix: DeleteService Name: port-aws-exporter-sync-ecs-service-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion clusterArn: $.detail.responseElements.service.clusterArn eventName: $.detail.eventName serviceArn: $.detail.responseElements.service.serviceArn serviceName: $.detail.responseElements.service.serviceName InputTemplate: >- { "resource_type": "AWS::ECS::Service", "region": "\"\"", "identifier": "if \"\" | startswith(\"Delete\") then \"\" else \"|\" end", "action": "if \"\" | startswith(\"Delete\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `ECS clusters` and `ECS services`. ## App Runner services[​](#app-runner-services "Direct link to App Runner services") In this step-by-step example, you will export your `App Runner services` to Port. 1. Create the following Port blueprint: * **App Runner** - will represent App Runner services from the AWS account. You may use the following definition: **App Runner blueprint (click to expand)** Create in Port ``` { "identifier": "apprunner", "description": "This blueprint represents an AWS App Runner service in our software catalog", "title": "AppRunner", "icon": "Service", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "status": { "type": "string", "enum": [ "CREATE_FAILED", "RUNNING", "DELETED", "DELETE_FAILED", "PAUSED", "OPERATION_IN_PROGRESS" ], "enumColors": { "CREATE_FAILED": "red", "RUNNING": "green", "DELETED": "red", "DELETE_FAILED": "red", "PAUSED": "yellow", "OPERATION_IN_PROGRESS": "blue" }, "title": "Status" }, "memory": { "type": "number", "title": "Memory" }, "cpu": { "type": "number", "title": "CPU" }, "serviceUrl": { "type": "string", "format": "url", "title": "Service URL" }, "egressType": { "type": "string", "enum": ["DEFAULT", "VPC"], "title": "Egress Type" }, "isPubliclyAccessible": { "type": "boolean", "title": "Is Publicly Accessible" }, "observabilityEnabled": { "type": "boolean", "title": "Observability Enabled" }, "autoDeploymentsEnabled": { "type": "boolean", "title": "Auto Deployments Enabled" }, "healthCheckConfiguration": { "type": "object", "title": "Health Check Configuration" }, "imageConfiguration": { "type": "object", "title": "Image Configuration" }, "imageIdentifier": { "type": "string", "title": "Image Identifier" }, "iamRole": { "type": "string", "format": "url", "title": "IAM Role", "icon": "Unlock" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::AppRunner::Service", "port": { "entity": { "mappings": [ { "identifier": ".ServiceId", "title": ".ServiceName", "blueprint": "apprunner", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .ServiceArn", "status": ".Status", "memory": ".InstanceConfiguration.Memory", "cpu": ".InstanceConfiguration.Cpu", "egressType": ".NetworkConfiguration.EgressConfiguration.EgressType", "isPubliclyAccessible": ".NetworkConfiguration.IngressConfiguration.IsPubliclyAccessible", "observabilityEnabled": ".ObservabilityConfiguration.ObservabilityEnabled", "autoDeploymentsEnabled": ".SourceConfiguration.AutoDeploymentsEnabled", "healthCheckConfiguration": ".HealthCheckConfiguration", "imageConfiguration": ".SourceConfiguration.ImageRepository.ImageConfiguration", "imageIdentifier": ".SourceConfiguration.ImageRepository.ImageIdentifier", "serviceUrl": "\"https://\" + .ServiceUrl", "iamRole": ".InstanceConfiguration.InstanceRoleArn | if . == null then null else \"https://console.aws.amazon.com/go/view?arn=\" + . end", "arn": ".ServiceArn" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": ["apprunner:DescribeService", "apprunner:ListServices"], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in App Runner services. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: 2010-09-09 Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AppRunner Service Operation Status Change source: - aws.apprunner Name: port-aws-exporter-sync-apprunner-events State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: operationStatus: $.detail.operationStatus region: $.region resource: $.resources[0] serviceId: $.detail.serviceId InputTemplate: >- { "resource_type": "AWS::AppRunner::Service", "region": "\"\"", "identifier": "if \"\" == \"DeleteServiceCompletedSuccessfully\" then \"\" else \"\" end", "action": "if \"\" == \"DeleteServiceCompletedSuccessfully\" then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `App Runner services`. ## Lambda functions [​](#lambda-functions--lambda- "Direct link to Lambda functions ") In this step-by-step example, you will export your `Lambda functions` to Port. 1. Create the following Port blueprint: * **Lambda** - will represent Lambda functions from the AWS account. You may use the following definition: **Lambda blueprint (click to expand)** Create in Port ``` { "identifier": "lambda", "description": "This blueprint represents an AWS Lambda function in our software catalog", "title": "Lambda", "icon": "Lambda", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "description": { "type": "string", "title": "Description" }, "memorySize": { "type": "number", "title": "Memory Size" }, "ephemeralStorageSize": { "type": "number", "title": "Ephemeral Storage Size" }, "timeout": { "type": "number", "title": "Timeout" }, "runtime": { "type": "string", "title": "Runtime" }, "packageType": { "type": "string", "enum": ["Image", "Zip"], "title": "Package Type" }, "environment": { "type": "object", "title": "Environment" }, "architectures": { "type": "array", "items": { "type": "string", "enum": ["x86_64", "arm64"] }, "title": "Architectures" }, "layers": { "type": "array", "title": "Layers" }, "tags": { "type": "array", "title": "Tags" }, "iamRole": { "type": "string", "format": "url", "title": "IAM Role", "icon": "Unlock" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::Lambda::Function", "port": { "entity": { "mappings": [ { "identifier": ".FunctionName", "title": ".FunctionName", "blueprint": "lambda", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "description": ".Description", "memorySize": ".MemorySize", "ephemeralStorageSize": ".EphemeralStorage.Size", "timeout": ".Timeout", "runtime": ".Runtime", "packageType": ".PackageType", "environment": ".Environment", "architectures": ".Architectures", "layers": ".Layers", "tags": ".Tags", "iamRole": "\"https://console.aws.amazon.com/go/view?arn=\" + .Role", "arn": ".Arn" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "lambda:ListFunctions", "lambda:GetFunction", "lambda:GetFunctionCodeSigningConfig" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in Lambda functions. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AWS API Call via CloudTrail source: - aws.lambda detail: eventSource: - lambda.amazonaws.com eventName: - prefix: CreateFunction - prefix: UpdateFunctionConfiguration - prefix: TagResource - prefix: UntagResource - prefix: DeleteFunction Name: port-aws-exporter-sync-lambda-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestFunctionName: $.detail.requestParameters.functionName requestResource: $.detail.requestParameters.resource responseFunctionName: $.detail.responseElements.functionName InputTemplate: |- { "resource_type": "AWS::Lambda::Function", "region": "\"\"", "identifier": "if \"\" != \"\" then \"\" elif \"\" != \"\" then \"\" | split(\":\")[-1] else \"\" end", "action": "if \"\" | test(\"DeleteFunction[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `Lambda functions`. ## SNS topics and SQS queues[​](#sns-topics-and-sqs-queues "Direct link to SNS topics and SQS queues") In this step-by-step example, you will export your `SNS topics` and `SQS queues` to Port. 1. Create the following Port blueprints: * **SNS** - will represent SNS topics from the AWS account. * **SQS** - will represent SQS queues from the AWS account. You may use the following definitions: **SQS blueprint (click to expand)** Create in Port ``` { "identifier": "sqs", "description": "This blueprint represents an AWS SQS service in our software catalog", "title": "SQS", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "fifoQueue": { "type": "boolean", "title": "Fifo Queue" }, "visibilityTimeout": { "type": "number", "title": "Visibility Timeout" }, "messageRetentionPeriod": { "type": "number", "title": "Message Retention Period" }, "maximumMessageSize": { "type": "number", "title": "Maximum Message Size" }, "receiveMessageWaitTimeSeconds": { "type": "number", "title": "Receive Message Wait Time Seconds" }, "delaySeconds": { "type": "number", "title": "Delay Seconds" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **SNS blueprint (click to expand)** Create in Port ``` { "identifier": "sns", "description": "This blueprint represents an AWS SNS topic in our software catalog", "title": "SNS", "icon": "SNS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "fifoTopic": { "type": "boolean", "title": "Fifo Topic" }, "subscriptions": { "type": "array", "title": "Subscriptions" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "sqs": { "target": "sqs", "required": false, "many": true } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::SQS::Queue", "port": { "entity": { "mappings": [ { "identifier": ".QueueName", "title": ".QueueName", "blueprint": "sqs", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "fifoQueue": ".FifoQueue // false", "visibilityTimeout": ".VisibilityTimeout", "messageRetentionPeriod": ".MessageRetentionPeriod", "maximumMessageSize": ".MaximumMessageSize", "receiveMessageWaitTimeSeconds": ".ReceiveMessageWaitTimeSeconds", "delaySeconds": ".DelaySeconds", "tags": ".Tags", "arn": ".Arn" } } ] } } }, { "kind": "AWS::SNS::Topic", "port": { "entity": { "mappings": [ { "identifier": ".TopicName", "title": ".TopicName", "blueprint": "sns", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .TopicArn", "fifoTopic": ".FifoTopic // false", "subscriptions": ".Subscription", "tags": ".Tags", "arn": ".TopicArn" }, "relations": { "sqs": ".Subscription // [] | map(select(.Protocol == \"sqs\") | .Endpoint | split(\":\")[-1])" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "sqs:GetQueueAttributes", "sqs:ListQueueTags", "sqs:ListQueues", "sns:GetDataProtectionPolicy", "sns:GetTopicAttributes", "sns:ListSubscriptionsByTopic", "sns:ListTagsForResource", "sns:ListTopics" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in SNS topics and SNS queues. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: 2010-09-09 Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.sns detail-type: - AWS API Call via CloudTrail detail: eventSource: - sns.amazonaws.com eventName: - prefix: CreateTopic - prefix: Subscribe - prefix: Unsubscribe - prefix: TagResource - prefix: UntagResource - prefix: DeleteTopic Name: port-aws-exporter-sync-sns-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestResourceArn: $.detail.requestParameters.resourceArn requestSubscriptionArn: $.detail.requestParameters.subscriptionArn requestTopicArn: $.detail.requestParameters.topicArn responseTopicArn: $.detail.responseElements.topicArn InputTemplate: |- { "resource_type": "AWS::SNS::Topic", "region": "\"\"", "identifier": "\"\" | if test(\"CreateTopic[^a-zA-Z]*$\") then \"\" elif test(\"Unsubscribe[^a-zA-Z]*$\") then \"\"[:\"\" | rindex(\":\")] elif test(\"TagResource|UntagResource[^a-zA-Z]*$\") then \"\" elif test(\"DeleteTopic[^a-zA-Z]*$\") then \"\" | split(\":\")[-1] else \"\" end", "action": "if \"\" | test(\"DeleteTopic[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } EventRule1: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.sqs detail-type: - AWS API Call via CloudTrail detail: eventSource: - sqs.amazonaws.com eventName: - prefix: CreateQueue - prefix: SetQueueAttributes - prefix: TagQueue - prefix: UntagQueue - prefix: DeleteQueue Name: port-aws-exporter-sync-sqs-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestQueueUrl: $.detail.requestParameters.queueUrl responseQueueUrl: $.detail.responseElements.queueUrl InputTemplate: |- { "resource_type": "AWS::SQS::Queue", "region": "\"\"", "identifier": "\"\" | if test(\"CreateQueue[^a-zA-Z]*$\") then \"\" elif test(\"DeleteQueue[^a-zA-Z]*$\") then \"\" | split(\"/\")[-1] else \"\" end", "action": "if \"\" | test(\"DeleteQueue[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `SNS queue` and its `SQS queues` subscriptions. ## S3 buckets[​](#s3-buckets "Direct link to S3 buckets") In this step-by-step example, you will export your `S3 buckets` to Port. 1. Create the following Port blueprint: * **S3** - will represent S3 buckets from the AWS account. You may use the following definition: **S3 blueprint (click to expand)** Create in Port ``` { "identifier": "s3", "description": "This blueprint represents an AWS S3 bucket in our software catalog", "title": "S3", "icon": "Bucket", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "regionalDomainName": { "type": "string", "title": "Regional Domain Name" }, "versioningStatus": { "type": "string", "title": "Versioning Status", "enum": ["Enabled", "Suspended"] }, "encryption": { "type": "array", "title": "Encryption" }, "lifecycleRules": { "type": "array", "title": "Lifecycle Rules" }, "publicAccess": { "type": "object", "title": "Public Access" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::S3::Bucket", "port": { "entity": { "mappings": [ { "identifier": ".BucketName", "title": ".BucketName", "blueprint": "s3", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "regionalDomainName": ".RegionalDomainName", "versioningStatus": ".VersioningConfiguration.Status", "encryption": ".BucketEncryption.ServerSideEncryptionConfiguration", "lifecycleRules": ".LifecycleConfiguration.Rules", "publicAccess": ".PublicAccessBlockConfiguration", "tags": ".Tags", "arn": ".Arn" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "S3:GetBucketWebsite", "s3:GetAccelerateConfiguration", "s3:GetAnalyticsConfiguration", "s3:GetBucketCORS", "s3:GetBucketLogging", "s3:GetBucketNotification", "s3:GetBucketObjectLockConfiguration", "s3:GetBucketOwnershipControls", "s3:GetBucketPublicAccessBlock", "s3:GetBucketTagging", "s3:GetBucketVersioning", "s3:GetEncryptionConfiguration", "s3:GetIntelligentTieringConfiguration", "s3:GetInventoryConfiguration", "s3:GetLifecycleConfiguration", "s3:GetMetricsConfiguration", "s3:GetReplicationConfiguration", "s3:ListAllMyBuckets", "s3:ListBucket" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in S3 buckets. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.s3 detail-type: - AWS API Call via CloudTrail detail: eventSource: - s3.amazonaws.com eventName: - prefix: CreateBucket - prefix: PutBucket - prefix: DeleteBucket Name: port-aws-exporter-sync-s3-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestBucketName: $.detail.requestParameters.bucketName InputTemplate: |- { "resource_type": "AWS::S3::Bucket", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" | test(\"DeleteBucket[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `S3 buckets`. ## API Gateway APIs[​](#api-gateway-apis "Direct link to API Gateway APIs") In this step-by-step example, you will export your `API Gateway APIs` to Port. 1. Create the following Port blueprint: * **API Gateway** - will represent API Gateway APIs from the AWS account. You may use the following definition: **API Gateway blueprint (click to expand)** Create in Port ``` { "identifier": "apigateway", "description": "This blueprint represents an AWS API Gateway API in our software catalog", "title": "API Gateway", "icon": "RestApi", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "description": { "type": "string", "title": "Description" }, "protocolType": { "type": "string", "title": "Protocol Type", "enum": ["HTTP", "WEBSOCKET", "REST"] }, "apiKeySourceType": { "type": "string", "title": "Api Key Source Type", "enum": ["HEADER", "AUTHORIZER"] }, "routeSelection": { "type": "string", "title": "Route Selection" }, "apiEndpoint": { "type": "string", "title": "Api Endpoint" }, "disableExecuteApi": { "type": "boolean", "title": "Disable Execute Api" }, "cors": { "type": "object", "title": "Cors Configuration" }, "endpointTypes": { "type": "array", "title": "Endpoint Types", "items": { "type": "string", "enum": ["EDGE", "REGIONAL", "PRIVATE"] } }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::ApiGateway::RestApi", "port": { "entity": { "mappings": [ { "identifier": ".RestApiId", "title": ".Name", "blueprint": "apigateway", "properties": { "description": ".Description", "protocolType": "\"REST\"", "apiKeySourceType": ".ApiKeySourceType", "disableExecuteApi": ".DisableExecuteApiEndpoint", "endpointTypes": ".EndpointConfiguration.Types", "tags": ".Tags" } } ] } } }, { "kind": "AWS::ApiGatewayV2::Api", "port": { "entity": { "mappings": [ { "identifier": ".ApiId", "title": ".Name", "blueprint": "apigateway", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=arn:aws:apigateway:\" + (.ApiEndpoint | split(\".\")[-3]) + \"::/apis/\" + .ApiId", "description": ".Description", "protocolType": ".ProtocolType", "routeSelection": ".RouteSelectionExpression", "apiEndpoint": ".ApiEndpoint", "disableExecuteApi": ".DisableExecuteApiEndpoint", "cors": ".CorsConfiguration", "tags": ".Tags | to_entries" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": ["apigateway:GET"], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in API Gateway APIs. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.apigateway detail-type: - AWS API Call via CloudTrail detail: eventSource: - apigateway.amazonaws.com eventName: - prefix: CreateRestApi - prefix: ImportRestApi - prefix: PutRestApi - prefix: UpdateRestApi - prefix: DeleteRestApi Name: port-aws-exporter-sync-apigateway-restapi-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestRestApiId: $.detail.requestParameters.restApiId responseRestApiId: $.detail.responseElements.id InputTemplate: |- { "resource_type": "AWS::ApiGateway::RestApi", "region": "\"\"", "identifier": "if \"\" != \"\" then \"\" else \"\" end", "action": "if \"\" | test(\"DeleteRestApi[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } EventRule1: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.apigateway detail-type: - AWS API Call via CloudTrail detail: eventSource: - apigateway.amazonaws.com eventName: - prefix: CreateApi - prefix: ImportApi - prefix: ReimportApi - prefix: UpdateApi - prefix: DeleteApi Name: port-aws-exporter-sync-apigateway-api-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestApiId: $.detail.requestParameters.apiId responseApiId: $.detail.responseElements.apiId InputTemplate: |- { "resource_type": "AWS::ApiGatewayV2::Api", "region": "\"\"", "identifier": "if \"\" != \"\" then \"\" else \"\" end", "action": "if \"\" | test(\"DeleteApi[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `API Gateway APIs`. ## Cloudfront distributions[​](#cloudfront-distributions "Direct link to Cloudfront distributions") In this step-by-step example, you will export your `Cloudfront distributions` to Port. Important Cloudfront is a global (not regional) service in AWS, that its events are recorded by AWS CloudTrail in the `us-east-1` region. Therefore, in order to automatically sync changes in Cloudfront distributions with an event rule (step 4 of this example), you need to choose one of the following methods: 1. Deploy the Port AWS exporter and the event rule in the `us-east-1` region. 2. Create a trail that will capture global service events in the same region that you've deployed the Port AWS exporter. For more information, read [here](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-concepts.html#cloudtrail-concepts-global-service-events). 1. Create the following Port blueprint: * **Cloudfront** - will represent Cloudfront distributions from the AWS account. You may use the following definition: **Cloudfront blueprint (click to expand)** Create in Port ``` { "identifier": "cloudfront", "description": "This blueprint represents an AWS Cloudfront distribution in our software catalog", "title": "Cloudfront", "icon": "Cloud", "schema": { "properties": { "link": { "type": "string", "title": "Link", "format": "url" }, "description": { "type": "string", "title": "Description" }, "staging": { "type": "boolean", "title": "Staging" }, "enabled": { "type": "boolean", "title": "Enabled" }, "httpVersion": { "type": "string", "title": "Http Version", "enum": ["http1.1", "http2", "http2and3", "http3"] }, "priceClass": { "type": "string", "title": "Price Class", "enum": ["PriceClass_100", "PriceClass_200", "PriceClass_All"] }, "domainName": { "type": "string", "title": "Domain Name" }, "aliases": { "type": "array", "title": "Aliases" }, "origins": { "type": "array", "title": "Origins" }, "viewerCertificate": { "type": "object", "title": "Viewer Certificate" }, "defaultCacheBehavior": { "type": "object", "title": "Default Cache Behavior" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::CloudFront::Distribution", "port": { "entity": { "mappings": [ { "identifier": ".Id", "title": ".Id", "blueprint": "cloudfront", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=arn:aws:cloudfront:::distribution/\" + .Id", "description": ".DistributionConfig.Comment", "staging": ".DistributionConfig.Staging", "enabled": ".DistributionConfig.Enabled", "httpVersion": ".DistributionConfig.HttpVersion", "priceClass": ".DistributionConfig.PriceClass", "domainName": ".DomainName", "aliases": ".DistributionConfig.Aliases", "origins": ".DistributionConfig.Origins", "viewerCertificate": ".DistributionConfig.ViewerCertificate", "defaultCacheBehavior": ".DistributionConfig.DefaultCacheBehavior", "tags": ".Tags" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "cloudfront:GetDistribution*", "cloudfront:ListDistributions*" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in Cloudfront distributions. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.cloudfront detail-type: - AWS API Call via CloudTrail detail: eventSource: - cloudfront.amazonaws.com eventName: - prefix: CreateDistribution - prefix: CopyDistribution - prefix: UpdateDistribution - prefix: DeleteDistribution Name: port-aws-exporter-sync-cloudfront-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestDistributionId: $.detail.requestParameters.id responseDistributionId: $.detail.responseElements.distribution.id InputTemplate: |- { "resource_type": "AWS::CloudFront::Distribution", "region": "\"\"", "identifier": "if \"\" != \"\" then \"\" else \"\" end", "action": "if \"\" | test(\"DeleteDistribution[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `Cloudfront distributions`. ## DynamoDB tables[​](#dynamodb-tables "Direct link to DynamoDB tables") In this step-by-step example, you will export your `DynamoDB tables` to Port. 1. Create the following Port blueprint: * **DynamoDB** - will represent DynamoDB tables from the AWS account. You may use the following definition: **DynamoDB blueprint (click to expand)** Create in Port ``` { "identifier": "dynamodb", "description": "This blueprint represents an AWS DynamoDB table in our software catalog", "title": "DynamoDB", "icon": "SQL", "schema": { "properties": { "link": { "type": "string", "title": "Link", "format": "url" }, "writeCapacityUnits": { "type": "number", "title": "Write Capacity Units" }, "readCapacityUnits": { "type": "number", "title": "Read Capacity Units" }, "deletionProtectionEnabled": { "type": "boolean", "title": "Deletion Protection Enabled" }, "pointInTimeRecoveryEnabled": { "type": "boolean", "title": "Point In Time Recovery Enabled" }, "ttlEnabled": { "type": "boolean", "title": "TTL Enabled" }, "ttlAttributeName": { "type": "string", "title": "TTL Attribute Name" }, "billingMode": { "type": "string", "title": "Billing Mode", "enum": ["PAY_PER_REQUEST", "PROVISIONED"] }, "attributeDefinitions": { "type": "array", "title": "Attribute Definitions" }, "keySchema": { "type": "array", "title": "Key Schema" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::DynamoDB::Table", "port": { "entity": { "mappings": [ { "identifier": ".TableName", "title": ".TableName", "blueprint": "dynamodb", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "writeCapacityUnits": ".ProvisionedThroughput.WriteCapacityUnits", "readCapacityUnits": ".ProvisionedThroughput.ReadCapacityUnits", "deletionProtectionEnabled": ".DeletionProtectionEnabled", "pointInTimeRecoveryEnabled": ".PointInTimeRecoverySpecification.PointInTimeRecoveryEnabled", "ttlEnabled": ".TimeToLiveSpecification.Enabled", "ttlAttributeName": ".TimeToLiveSpecification.AttributeName", "billingMode": ".BillingMode", "attributeDefinitions": ".AttributeDefinitions", "keySchema": ".KeySchema", "tags": ".Tags", "arn": ".Arn" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "dynamodb:DescribeContinuousBackups", "dynamodb:DescribeContributorInsights", "dynamodb:DescribeTable", "dynamodb:DescribeTimeToLive", "dynamodb:ListTables", "dynamodb:ListTagsOfResource" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in DynamoDB tables. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.dynamodb detail-type: - AWS API Call via CloudTrail detail: eventSource: - dynamodb.amazonaws.com eventName: - prefix: CreateTable - prefix: UpdateTable - prefix: UpdateTimeToLive - prefix: UpdateContinuousBackups - prefix: TagResource - prefix: UntagResource - prefix: DeleteTable Name: port-aws-exporter-sync-dynamodb-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestResourceArn: $.detail.requestParameters.resourceArn requestTableName: $.detail.requestParameters.tableName InputTemplate: |- { "resource_type": "AWS::DynamoDB::Table", "region": "\"\"", "identifier": "if \"\" != \"\" then \"\" else \"\" | split(\"/\")[-1] end", "action": "if \"\" | test(\"DeleteTable[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `DynamoDB tables`. ## RDS instances[​](#rds-instances "Direct link to RDS instances") In this step-by-step example, you will export your `RDS instances` to Port. 1. Create the following Port blueprint: * **RDS** - will represent RDS instances from the AWS account. You may use the following definition: **RDS blueprint (click to expand)** Create in Port ``` { "identifier": "rds", "description": "This blueprint represents an AWS RDS instance in our software catalog", "title": "RDS", "icon": "SQL", "schema": { "properties": { "link": { "type": "string", "title": "Link", "format": "url" }, "engine": { "type": "string", "title": "Engine" }, "engineVersion": { "type": "string", "title": "Engine Version" }, "storageType": { "type": "string", "title": "Storage Type", "enum": ["gp2", "gp3", "io1", "standard", "aurora"] }, "dbInstanceClass": { "type": "string", "title": "DB Instance Class" }, "availabilityZone": { "type": "string", "title": "Availability Zone" }, "dbParameterGroup": { "type": "string", "title": "DB Parameter Group" }, "optionGroup": { "type": "string", "title": "Option Group" }, "dbSubnetGroup": { "type": "string", "title": "DB Subnet Group" }, "masterUsername": { "type": "string", "title": "Master Username" }, "allocatedStorage": { "type": "string", "title": "Allocated Storage" }, "maxAllocatedStorage": { "type": "number", "title": "Max Allocated Storage" }, "backupRetentionPeriod": { "type": "number", "title": "Backup Retention Period" }, "monitoringInterval": { "type": "number", "title": "Monitoring Interval", "enum": [0, 1, 5, 10, 15, 30, 60] }, "multiAZ": { "type": "boolean", "title": "Multi AZ" }, "storageEncrypted": { "type": "boolean", "title": "Storage Encrypted" }, "enablePerformanceInsights": { "type": "boolean", "title": "Enable Performance Insights" }, "autoMinorVersionUpgrade": { "type": "boolean", "title": "Auto Minor Version Upgrade" }, "deletionProtection": { "type": "boolean", "title": "Deletion Protection" }, "publiclyAccessible": { "type": "boolean", "title": "Publicly Accessible" }, "certificateValidTill": { "type": "string", "title": "Certificate Valid Till", "format": "date-time" }, "certificateCA": { "type": "string", "title": "Certificate CA" }, "preferredBackupWindow": { "type": "string", "title": "Preferred Backup Window" }, "preferredMaintenanceWindow": { "type": "string", "title": "Preferred Maintenance Window" }, "endpoint": { "type": "object", "title": "Endpoint" }, "tags": { "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::RDS::DBInstance", "port": { "entity": { "mappings": [ { "identifier": ".DBInstanceIdentifier", "title": ".DBInstanceIdentifier", "blueprint": "rds", "properties": { "link": "if .Engine == \"docdb\" then \"https://console.aws.amazon.com/docdb/home?region=\" + (.DBInstanceIdentifier | split(\":\")[3]) + \"#instance-details/\" + .DBInstanceIdentifier else \"https://console.aws.amazon.com/go/view?arn=\" + .DBInstanceArn end", "engine": ".Engine", "engineVersion": ".EngineVersion", "storageType": ".StorageType", "dbInstanceClass": ".DBInstanceClass", "availabilityZone": ".AvailabilityZone", "dbParameterGroup": ".DBParameterGroupName", "optionGroup": ".OptionGroupName", "dbSubnetGroup": ".DBSubnetGroupName", "masterUsername": ".MasterUsername", "allocatedStorage": ".AllocatedStorage", "maxAllocatedStorage": ".MaxAllocatedStorage", "backupRetentionPeriod": ".BackupRetentionPeriod", "monitoringInterval": ".MonitoringInterval", "multiAZ": ".MultiAZ", "storageEncrypted": ".StorageEncrypted", "enablePerformanceInsights": ".EnablePerformanceInsights", "autoMinorVersionUpgrade": ".AutoMinorVersionUpgrade", "deletionProtection": ".DeletionProtection", "publiclyAccessible": ".PubliclyAccessible", "certificateValidTill": ".CertificateDetails.ValidTill", "certificateCA": ".CertificateDetails.CAIdentifier", "preferredBackupWindow": ".PreferredBackupWindow", "preferredMaintenanceWindow": ".PreferredMaintenanceWindow", "endpoint": ".Endpoint", "tags": ".Tags", "arn": ".DBInstanceArn" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ec2:DescribeAccountAttributes", "ec2:DescribeAvailabilityZones", "ec2:DescribeInternetGateways", "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeVpcAttribute", "ec2:DescribeVpcs", "rds:DescribeDBInstances" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in RDS instances. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.rds detail-type: - AWS API Call via CloudTrail detail: eventSource: - rds.amazonaws.com eventName: - prefix: CreateDBInstance - prefix: CreateDBInstanceReadReplica - prefix: RestoreDBInstanceFromDBSnapshot - prefix: RestoreDBInstanceFromS3 - prefix: RestoreDBInstanceToPointInTime - prefix: ModifyDBInstance - prefix: AddTagsToResource - prefix: RemoveTagsFromResource - prefix: DeleteDBInstance Name: port-aws-exporter-sync-rds-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestDBInstanceIdentifier: $.detail.requestParameters.dBInstanceIdentifier requestResourceName: $.detail.requestParameters.resourceName requestTargetDBInstanceIdentifier: $.detail.requestParameters.targetDBInstanceIdentifier InputTemplate: |- { "resource_type": "AWS::RDS::DBInstance", "region": "\"\"", "identifier": "if \"\" != \"\" then \"\" elif \"\" != \"\" then \"\" else \"\" | split(\":\")[-1] end", "action": "if \"\" | test(\"DeleteDBInstance[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `RDS instances`. ## Step Functions state machines[​](#step-functions-state-machines "Direct link to Step Functions state machines") In this step-by-step example, you will export your `Step Functions state machines` to Port. 1. Create the following Port blueprint: * **Step Functions** - will represent Step Functions state machines from the AWS account. You may use the following definition: **Step Functions blueprint (click to expand)** Create in Port ``` { "identifier": "stepfunctions", "description": "This blueprint represents an AWS Step Functions state machine in our software catalog", "title": "Step Functions", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "title": "Link", "format": "url" }, "type": { "type": "string", "title": "Type", "enum": ["STANDARD", "EXPRESS"] }, "definitionS3Location": { "type": "string", "title": "Definition S3 Location" }, "definitionObject": { "type": "object", "title": "Definition Object" }, "definitionSubstitutions": { "type": "object", "title": "Definition Substitutions" }, "tags": { "type": "array", "title": "Tags" }, "iamRole": { "type": "string", "title": "IAM Role", "format": "url", "icon": "Unlock" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::StepFunctions::StateMachine", "port": { "entity": { "mappings": [ { "identifier": ".StateMachineName", "title": ".StateMachineName", "blueprint": "stepfunctions", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "type": ".StateMachineType", "definitionS3Location": ".DefinitionS3Location", "definitionObject": ".DefinitionString | fromjson", "definitionSubstitutions": ".DefinitionSubstitutions", "tags": ".Tags", "iamRole": ".RoleArn | if . == null then null else \"https://console.aws.amazon.com/go/view?arn=\" + . end", "arn": ".Arn" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "states:DescribeStateMachine", "states:ListStateMachines", "states:ListTagsForResource" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in Step Functions state machines. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.states detail-type: - AWS API Call via CloudTrail detail: eventSource: - states.amazonaws.com eventName: - prefix: CreateStateMachine - prefix: UpdateStateMachine - prefix: TagResource - prefix: UntagResource - prefix: DeleteStateMachine Name: port-aws-exporter-sync-stepfunctions-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestResourceArn: $.detail.requestParameters.resourceArn requestStateMachineArn: $.detail.requestParameters.stateMachineArn responseStateMachineArn: $.detail.responseElements.stateMachineArn InputTemplate: |- { "resource_type": "AWS::StepFunctions::StateMachine", "region": "\"\"", "identifier": "\"\" | if test(\"DeleteStateMachine[^a-zA-Z]*$\") then \"\" | split(\":\")[-1] elif \"\" != \"\" then \"\" elif \"\" != \"\" then \"\" else \"\" end", "action": "if \"\" | test(\"DeleteStateMachine[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `Step Functions state machines`. ## Elastic Beanstalk applications and environments[​](#elastic-beanstalk-applications-and-environments "Direct link to Elastic Beanstalk applications and environments") In this step-by-step example, you will export your `Elastic Beanstalk applications and environments` to Port. 1. Create the following Port blueprint: * **Beanstalk Application** - will represent Elastic Beanstalk applications from the AWS account. * **Beanstalk Environment** - will represent Elastic Beanstalk environments from the AWS account. You may use the following definitions: **Beanstalk Application blueprint (click to expand)** Create in Port ``` { "identifier": "beanstalkApplication", "description": "This blueprint represents an AWS Elastic Beanstalk application in our software catalog", "title": "Beanstalk Application", "icon": "AWS", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "versionMaxCountRule": { "type": "object", "title": "Version Max Count Rule" }, "versionMaxAgeRule": { "type": "object", "title": "Version Max Age Rule" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Beanstalk Environment blueprint (click to expand)** Create in Port ``` { "identifier": "beanstalkEnvironment", "description": "This blueprint represents an AWS Elastic Beanstalk environment in our software catalog", "title": "Beanstalk Environment", "icon": "AWS", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "solutionStackName": { "type": "string", "title": "Solution Stack Name" }, "tierName": { "type": "string", "title": "Tier Name", "enum": ["WebServer", "Worker"] }, "tierType": { "type": "string", "title": "Tier Type", "enum": ["Standard", "SQS/HTTP"] }, "endpointURL": { "type": "string", "title": "Endpoint URL" }, "cnamePrefix": { "type": "string", "title": "CNAME Prefix" }, "platformArn": { "type": "string", "title": "Platform Arn" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "beanstalkApplication": { "target": "beanstalkApplication", "required": false, "many": false } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::ElasticBeanstalk::Application", "port": { "entity": { "mappings": [ { "identifier": ".ApplicationName", "title": ".ApplicationName", "blueprint": "beanstalkApplication", "properties": { "description": ".Description", "versionMaxCountRule": ".ResourceLifecycleConfig.VersionLifecycleConfig.MaxAgeRule", "versionMaxAgeRule": ".ResourceLifecycleConfig.VersionLifecycleConfig.MaxCountRule" } } ] } } }, { "kind": "AWS::ElasticBeanstalk::Environment", "port": { "entity": { "mappings": [ { "identifier": ".EnvironmentName", "title": ".EnvironmentName", "blueprint": "beanstalkEnvironment", "properties": { "description": ".Description", "cnamePrefix": ".CNAMEPrefix", "solutionStackName": ".SolutionStackName", "platformArn": ".PlatformArn", "tierName": ".Tier.Name", "tierType": ".Tier.Type", "endpointURL": ".EndpointURL", "tags": ".Tags" }, "relations": { "beanstalkApplication": ".ApplicationName" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "elasticbeanstalk:DescribeApplications", "elasticbeanstalk:DescribeConfigurationSettings", "elasticbeanstalk:DescribeEnvironments", "elasticbeanstalk:ListTagsForResource" ], "Resource": "*" } ] } ``` 4. Optional: Create an event rule to trigger automatic syncing of changes in Elastic Beanstalk applications and environments. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticbeanstalk detail-type: - AWS API Call via CloudTrail detail: eventSource: - elasticbeanstalk.amazonaws.com eventName: - prefix: CreateApplication - prefix: UpdateApplication - prefix: UpdateApplicationResourceLifecycle - prefix: DeleteApplication Name: port-aws-exporter-sync-beanstalk-apps-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestApplicationName: $.detail.requestParameters.applicationName InputTemplate: |- { "resource_type": "AWS::ElasticBeanstalk::Application", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" | test(\"DeleteApplication[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } EventRule1: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticbeanstalk detail-type: - Elastic Beanstalk resource status change Name: port-aws-exporter-sync-beanstalk-envs-events State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: environmentName: $.detail.EnvironmentName region: $.region status: $.detail.Status InputTemplate: |- { "resource_type": "AWS::ElasticBeanstalk::Environment", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" == \"Environment termination successful\" then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `Elastic Beanstalk applications and environments`. ## CloudFormation Stacks[​](#cloudformation-stacks "Direct link to CloudFormation Stacks") In this step-by-step example, you will export your `CloudFormation Stacks` to Port. 1. Create the following Port blueprint: * **CloudFormation Stack** - will represent CloudFormation Stacks from the AWS account. You may use the following definition: **CloudFormationStack blueprint (click to expand)** Create in Port ``` { "identifier": "cloudFormationStack", "description": "This blueprint represents a service in our software catalog", "title": "CloudFormation Stack", "icon": "Microservice", "schema": { "properties": { "createdAt": { "type": "string", "title": "Creation Time" }, "status": { "title": "Status", "description": "The current status of the Stack", "type": "string", "enum": [ "CREATE_IN_PROGRESS", "CREATE_FAILED", "CREATE_COMPLETE", "ROLLBACK_IN_PROGRESS", "ROLLBACK_FAILED", "ROLLBACK_COMPLETE", "DELETE_IN_PROGRESS", "DELETE_FAILED", "UPDATE_IN_PROGRESS", "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", "UPDATE_COMPLETE", "UPDATE_FAILED", "UPDATE_ROLLBACK_IN_PROGRESS", "UPDATE_ROLLBACK_FAILED", "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS", "UPDATE_ROLLBACK_COMPLETE", "REVIEW_IN_PROGRESS", "IMPORT_IN_PROGRESS", "IMPORT_COMPLETE", "IMPORT_ROLLBACK_IN_PROGRESS", "IMPORT_ROLLBACK_FAILED", "IMPORT_ROLLBACK_COMPLETE" ], "enumColors": { "CREATE_IN_PROGRESS": "orange", "CREATE_FAILED": "red", "CREATE_COMPLETE": "green", "ROLLBACK_IN_PROGRESS": "orange", "ROLLBACK_FAILED": "red", "ROLLBACK_COMPLETE": "green", "UPDATE_IN_PROGRESS": "orange", "UPDATE_FAILED": "red", "UPDATE_COMPLETE": "green" } }, "template": { "title": "Template", "type": "string", "format": "yaml" }, "tags": { "items": { "type": "object" }, "title": "Tags", "type": "array" }, "link": { "title": "link", "description": "The aws console stack url", "type": "string", "format": "url" }, "lastUpdated": { "type": "string", "title": "Last Updated" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "kind": "AWS::CloudFormation::Stack", "port": { "entity": { "mappings": [ { "identifier": ".StackName", "title": ".StackName", "blueprint": "cloudFormationStack", "properties": { "lastUpdated": ".LastUpdatedTime", "createdAt": ".CreationTime", "status": ".StackStatus", "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .StackId", "template": ".TemplateBody", "tags": ".Tags" } } ] } } } ``` 3. Update the exporter's `IAM policy`: **IAM Policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "cloudformation:DescribeStacks", "cloudformation:DescribeStackResources", "cloudformation:ListStacks", "cloudformation:GetTemplate" ], "Resource": "*" } ] } ``` 4. Optional: Create 2 event rules to trigger automatic syncing of changes in CloudFormation Stacks. You may use the following CloudFormation Template: **Event Rule CloudFormation Template (click to expand)** ``` EventRule0: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AWS API Call via CloudTrail source: - aws.cloudformation detail: eventSource: - cloudformation.amazonaws.com eventName: - prefix: CreateStack - prefix: UpdateStack - prefix: DeleteStack Name: port-aws-exporter-sync-cloudformation-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: eventName: $.detail.eventName awsRegion: $.detail.awsRegion stackName: $.detail.requestParameters.stackName InputTemplate: |- { "resource_type": "AWS::CloudFormation::Stack", "region": "\"\"", "identifier": "if \"\" | startswith(\"arn:\") then \"\" | split(\"/\")[1] else \"\" end", "action": "if \"\" | test(\"DeleteStack[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } EventRule1: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - CloudFormation Stack Status Change source: - aws.cloudformation Name: port-aws-exporter-sync-cloudformation-status-change-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: region: $.region stackId: $.detail.stack-id status: $.detail.status-details.status InputTemplate: |- { "resource_type": "AWS::CloudFormation::Stack", "region": "\"\"", "identifier": "\"\" | split(\"/\")[1]", "action": "if \"\" == \"DELETE_COMPLETE\" then \"delete\" else \"upsert\" end" } ``` IMPORTANT The AWS CloudFormation API can retrieve up to 100 resources per CloudFormation Stack. For more information about the CloudFormation API, see the [API Reference](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/index.html). ### Relations between CloudFormation Stacks and AWS resources[​](#relations-between-cloudformation-stacks-and-aws-resources "Direct link to Relations between CloudFormation Stacks and AWS resources") In order to connect between CloudFormation Stacks and their affected resources, you'll need to update the blueprint and the exporter configuration. Here's an example showing how to connect CloudFormation Stacks and Lambda functions: **Add relations to the blueprint (click to expand)** ``` { "relations": { "lambdas": { "title": "Created Lambdas", "description": "The Lambda functions created from the CloudFormation Stack", "target": "lambda", "required": false, "many": true } } } ``` **Add relations to the exporter config.json (click to expand)** ``` { ... "mappings": [ {...}, "relations": { "lambdas": ".StackResources // [] | map(select(.ResourceType == \"AWS::Lambda::Function\")) | if . == [] then [] else .[].PhysicalResourceId end" }] } ``` Make sure your [Lambda function configuration](#lambda) appears before your CloudFormation definition in the `config.json`. Done! soon, you will be able to see any `CloudFormation Stacks`. ## EC2 instances[​](#ec2-instances "Direct link to EC2 instances") In this step-by-step example, you will export your `EC2 instances` to Port. 1. Create the following Port blueprint: * **EC2 Instance** - will represent EC2 instances from the AWS account. You may use the following definitions: **EC2 instance blueprint (click to expand)** Create in Port ``` { "identifier": "ec2Instance", "description": "This blueprint represents an AWS EC2 instance in our software catalog", "title": "EC2 Instance", "icon": "EC2", "schema": { "properties": { "architecture": { "type": "string", "title": "Architecture", "enum": ["i386", "x86_64", "arm64", "x86_64_mac", "arm64_mac"] }, "availabilityZone": { "type": "string", "title": "Availability Zone" }, "link": { "type": "string", "title": "Link", "format": "url" }, "platform": { "type": "string", "title": "Platform" }, "state": { "type": "string", "title": "State", "enum": [ "pending", "running", "shutting-down", "terminated", "stopping", "stopped" ], "enumColors": { "pending": "yellow", "running": "green", "shutting-down": "pink", "stopped": "purple", "stopping": "orange", "terminated": "red" } }, "tags": { "type": "array", "title": "Tags" }, "type": { "type": "string", "title": "Instance Type" }, "vpcId": { "type": "string", "title": "VPC ID" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::EC2::Instance", "selector": { "query": ".State.Name | startswith(\"terminated\") | not" }, "port": { "entity": { "mappings": [ { "identifier": ".InstanceId", "title": ".Tags[]? | select(.Key == \"Name\") | .Value", "blueprint": "ec2Instance", "properties": { "state": ".State.Name", "type": ".InstanceType", "vpcId": ".VpcId", "link": "\"https://console.aws.amazon.com/ec2/home?region=\" + .Placement.AvailabilityZone[:-1] + \"#InstanceDetails:instanceId=\" + .InstanceId", "availabilityZone": ".Placement.AvailabilityZone", "platform": ".PlatformDetails", "architecture": ".Architecture", "tags": ".Tags" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": ["ec2:DescribeInstances", "ec2:DescribeInstanceStatus"], "Resource": "*" } ] } ``` 4. Optional: create an event rule to trigger automatic syncing of changes in EC2 instances. You may use the following CloudFormation template: **Event rule CloudFormation template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EC2InstanceTagsEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.ec2 detail-type: - AWS API Call via CloudTrail detail: eventSource: - ec2.amazonaws.com eventName: - prefix: DeleteTags - prefix: CreateTags Name: port-aws-exporter-sync-ec2-tags-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestInstanceId: $.detail.requestParameters.resourcesSet.items[0].resourceId InputTemplate: |- { "resource_type": "AWS::EC2::Instance", "region": "\"\"", "identifier": "\"\"", "action": "\"upsert\"" } EC2InstanceStateChangeEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - EC2 Instance State-change Notification source: - aws.ec2 Name: port-aws-exporter-sync-ec2-instance-status-change-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: region: $.region instanceId: $.detail.instance-id status: $.detail.state InputTemplate: |- { "resource_type": "AWS::EC2::Instance", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" == \"terminated\" then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `EC2 instances` ## Elastic load balancers[​](#elastic-load-balancers "Direct link to Elastic load balancers") In this step-by-step example, you will export your `Load balancers` to Port. 1. Create the following Port blueprint: * **Load Balancer** - will represent load balancers from the AWS account. You may use the following definitions: **LoadBalancer blueprint (click to expand)** Create in Port ``` { "identifier": "loadBalancer", "description": "This blueprint represents an AWS Load Balancer in our software catalog", "title": "Load Balancer", "icon": "AWS", "schema": { "properties": { "state": { "type": "string", "title": "State", "default": "active", "enum": ["provisioning", "active", "failed", "active_impaired"], "enumColors": { "active": "green", "failed": "red", "provisioning": "yellow", "active_impaired": "orange" } }, "type": { "type": "string", "title": "Type", "default": "application", "enum": ["application", "network", "gateway"] }, "scheme": { "type": "string", "title": "Scheme" }, "vpcId": { "type": "string", "title": "Vpc ID" }, "availabilityZones": { "type": "array", "title": "Availability Zones" }, "dnsName": { "type": "string", "title": "DNS Name" }, "securityGroup": { "type": "array", "title": "Security Group" }, "arn": { "type": "string", "title": "ARN" }, "link": { "type": "string", "format": "url", "title": "Link" }, "listeners": { "type": "array", "title": "Listeners" }, "attributes": { "type": "array", "title": "Attributes" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "region": { "title": "Region", "target": "region", "required": false, "many": false } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::ElasticLoadBalancingV2::LoadBalancer", "port": { "entity": { "mappings": [ { "identifier": ".LoadBalancerName", "title": ".LoadBalancerName", "blueprint": "loadBalancer", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .LoadBalancerArn", "state": ".State.Code", "type": ".Type", "scheme": ".Scheme", "vpcId": ".VpcId", "availabilityZones": ".AvailabilityZones", "dnsName": ".DNSName", "securityGroup": ".SecurityGroups", "listeners": ".Listeners", "attributes": ".Attributes", "tags": ".Tags", "arn": ".LoadBalancerArn" }, "relations": { "region": ".LoadBalancerArn | split(\":\") | .[3]" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "elasticloadbalancing:DescribeLoadBalancers", "elasticloadbalancing:DescribeLoadBalancerAttributes", "elasticloadbalancing:DescribeListeners", "elasticloadbalancing:DescribeTags", "ec2:DescribeSecurityGroups" ], "Resource": "*" } ] } ``` 4. Optional: create an event rule to trigger automatic syncing of changes in load balancers. You may use the following CloudFormation template: **Event rule CloudFormation template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: LoadBalancerCreateEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticloadbalancing detail-type: - AWS API Call via CloudTrail detail: eventSource: - elasticloadbalancing.amazonaws.com eventName: - prefix: CreateLoadBalancer Name: port-aws-exporter-sync-elb-create-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestElbName: $.detail.requestParameters.name InputTemplate: |- { "resource_type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" | test(\"DeleteLoadBalancer[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } LoadBalancerUpdateEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticloadbalancing detail-type: - AWS API Call via CloudTrail detail: eventSource: - elasticloadbalancing.amazonaws.com eventName: - prefix: DeleteLoadBalancer - prefix: SetSecurityGroups - prefix: ModifyLoadBalancerAttributes Name: port-aws-exporter-sync-elb-update-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestElbArn: $.detail.requestParameters.loadBalancerArn InputTemplate: |- { "resource_type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "region": "\"\"", "identifier": "\"\" | split(\"/\") | .[2]", "action": "if \"\" | test(\"DeleteLoadBalancer[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } LoadBalancerTagsEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticloadbalancing detail-type: - AWS API Call via CloudTrail detail: eventSource: - elasticloadbalancing.amazonaws.com eventName: - prefix: RemoveTags - prefix: AddTags Name: port-aws-exporter-sync-elb-tags-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestElbArn: $.detail.requestParameters.resourceArns[0] InputTemplate: |- { "resource_type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "region": "\"\"", "identifier": "\"\" | split(\"/\") | .[2]", "action": "if \"\" | test(\"DeleteLoadBalancer[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } LoadBalancerUpsertListenersEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticloadbalancing detail-type: - AWS API Call via CloudTrail detail: eventSource: - elasticloadbalancing.amazonaws.com eventName: - prefix: CreateListener - prefix: ModifyListener Name: port-aws-exporter-sync-elb-upsert-listener-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName responseElbArn: $.detail.responseElements.listeners[0].loadBalancerArn InputTemplate: |- { "resource_type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "region": "\"\"", "identifier": "\"\" | split(\"/\") | .[2]", "action": "if \"\" | test(\"DeleteLoadBalancer[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } LoadBalancerDeleteListenersEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticloadbalancing detail-type: - AWS API Call via CloudTrail detail: eventSource: - elasticloadbalancing.amazonaws.com eventName: - prefix: DeleteListener Name: port-aws-exporter-sync-elb-delete-listener-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName requestElbArn: $.detail.requestParameters.listenerArn InputTemplate: |- { "resource_type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "region": "\"\"", "identifier": "\"\" | split(\"/\") | .[2]", "action": "if \"\" | test(\"DeleteLoadBalancer[^a-zA-Z]*$\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `Load balancers` ## EKS clusters[​](#eks-clusters "Direct link to EKS clusters") In this step-by-step example, you will export your `EKS clusters` to Port. 1. Create the following Port blueprint: **EKS Cluster** - will represent EKS clusters from the AWS account. You may use the following definitions: **EKS blueprint (click to expand)** Create in Port ``` { "identifier": "eks", "description": "", "title": "EKS Cluster", "icon": "Service", "schema": { "properties": { "name": { "type": "string", "title": "Name" }, "roleArn": { "type": "string", "title": "Role ARN" }, "tags": { "type": "array", "title": "Tags" }, "version": { "type": "string", "title": "Version" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "region": { "title": "Region", "target": "region", "required": false, "many": false } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::EKS::Cluster", "port": { "entity": { "mappings": [ { "identifier": ".Name", "title": ".Name", "blueprint": "eks", "properties": { "name": ".Name", "roleArn": ".RoleArn", "version": ".Version", "tags": ".Tags" } } ], "relations": { "region": ".Arn | split(\":\")[3]", "cloudAccount": ".Arn | split(\":\")[4]" } } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": ["eks:DescribeCluster", "eks:ListClusters"], "Resource": "*" } ] } ``` 4. Optional: create an event rule to trigger automatic syncing of changes in EKS clusters. You may use the following CloudFormation template: **Event rule CloudFormation template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: EksClusterEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AWS API Call via CloudTrail source: - aws.eks detail: eventSource: - eks.amazonaws.com eventName: - prefix: CreateCluster - prefix: DeleteCluster Name: port-aws-exporter-sync-eks-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion clusterArn: $.detail.responseElements.cluster.arn eventName: $.detail.eventName clusterName: $.detail.responseElements.cluster.name InputTemplate: >- { "resource_type": "AWS::EKS::Cluster", "region": "", "identifier": "", "action": "if \"\" | startswith(\"Delete\") then \"delete\" else \"upsert\" end" } ``` Done! soon, you will be able to see any `EKS clusters` ## ECR repositories[​](#ecr-repositories "Direct link to ECR repositories") In this step-by-step example, you will export your `ECR repositories` to Port. 1. Create the following Port blueprint: **ECR Repository** - will represent ECR repositories from the AWS account. You may use the following definitions: **ECR Repository blueprint (click to expand)** Create in Port ``` { "identifier": "ecr_repository", "title": "ECR Repository", "icon": "Service", "schema": { "properties": { "link": { "title": "Link", "type": "string", "format": "url" }, "imageTagMutability": { "title": "Image Tag Mutability", "type": "string" }, "scanningConfiguration": { "title": "Scanning Configuration", "type": "object" }, "repositoryArn": { "title": "Repository ARN", "type": "string" }, "repositoryUri": { "title": "Repository URI", "type": "string" }, "encryptionConfiguration": { "title": "Encryption Configuration", "type": "object" }, "lifecyclePolicy": { "title": "Lifecycle Policy", "type": "object" }, "tags": { "title": "Tags", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "region": { "title": "Region", "target": "region", "required": false, "many": false } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::ECR::Repository", "port": { "entity": { "mappings": [ { "identifier": ".RepositoryName", "title": ".RepositoryName", "blueprint": "ecr_repository", "properties": { "imageTagMutability": ".ImageTagMutability", "scanningConfiguration": ".ImageScanningConfiguration", "repositoryArn": ".Arn", "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "repositoryUri": ".RepositoryUri", "encryptionConfiguration": ".EncryptionConfiguration", "lifecyclePolicy": ".LifecyclePolicy", "tags": ".Tags" }, "relations": { "region": ".Arn | split(\":\") | .[3]" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "ecr:DescribeRepositories", "ecr:GetLifecyclePolicy", "ecr:GetRepositoryPolicy", "ecr:ListTagsForResource" ], "Resource": "*" } ] } ``` 4. Optional: create an event rule to trigger automatic syncing of changes in ECR repositories. You may use the following CloudFormation template: **Event rule CloudFormation template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: ECRRepositoryEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AWS API Call via CloudTrail source: - aws.ecr detail: eventSource: - ecr.amazonaws.com eventName: - prefix: CreateRepository - prefix: DeleteRepository - prefix: DeleteLifecyclePolicy - prefix: PutLifecyclePolicy Name: port-aws-exporter-sync-ecr-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName repositoryName: $.detail.requestParameters.repositoryName InputTemplate: >- { "resource_type": "AWS::ECR::Repository", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" | startswith(\"DeleteRepository\") then \"delete\" else \"upsert\" end" } ECRRepositoryTagRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.ecr detail-type: - AWS API Call via CloudTrail detail: eventSource: - ecr.amazonaws.com eventName: - prefix: TagResource - prefix: UntagResource Name: port-aws-exporter-sync-ecr-tags-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName resourceArn: $.detail.requestParameters.resourceArn InputTemplate: |- { "resource_type": "AWS::ECR::Repository", "region": "\"\"", "identifier": "\"\" | split(\"/\") | .[-1]", "action": "\"upsert\"" } ``` Done! soon, you will be able to see any `ECR repositories` ### Using Python script[​](#using-python-script "Direct link to Using Python script") Alternatively you can use a Python script to export your ECR repositories and images to Port. The script is available in the [example-ecr-images](https://github.com/port-labs/example-ecr-images) repository. ## Elasticache serverless cache[​](#elasticache-serverless-cache "Direct link to Elasticache serverless cache") In this step-by-step example, you will export your `Serverless cache` to Port. 1. Create the following Port blueprint: **Serverless cache** - will represent Serverless cache from the AWS account. You may use the following definitions: **Serverless cache blueprint (click to expand)** Create in Port ``` { "identifier": "aws_elasticache", "title": "Serverless Cache", "icon": "Service", "schema": { "properties": { "status": { "title": "Status", "type": "string" }, "description": { "title": "Description", "type": "string" }, "engine": { "title": "Engine", "type": "string" }, "tags": { "title": "Tags", "type": "array" }, "link": { "title": "Link", "type": "string", "format": "url" }, "majorEngineVersion": { "title": "Major Engine Version", "type": "string" }, "arn": { "title": "ARN", "type": "string" }, "createdTime": { "title": "Created Time", "type": "string", "format": "date-time" }, "dailySnapshotTime": { "title": "Daily Snapshot Time", "type": "string" }, "readerEndpoint": { "title": "Reader Endpoint", "type": "object" }, "endpoint": { "title": "Endpoint", "type": "object" }, "securityGroup": { "title": "Security Group", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "region": { "title": "Region", "target": "region", "required": false, "many": false } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::ElastiCache::ServerlessCache", "port": { "entity": { "mappings": [ { "identifier": ".ServerlessCacheName", "title": ".ServerlessCacheName", "blueprint": "aws_elasticache", "properties": { "status": ".Status", "description": ".Description", "majorEngineVersion": ".MajorEngineVersion", "createdTime": ".CreateTime", "dailySnapshotTime": ".DailySnapshotTime", "readerEndpoint": ".ReaderEndpoint", "endpoint": ".Endpoint", "securityGroup": ".SecurityGroupIds", "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .ARN", "arn": ".ARN", "engine": ".Engine", "tags": ".Tags" }, "relations": { "region": ".ARN | split(\":\") | .[3]" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "elasticache:DescribeServerlessCaches", "elasticache:ListTagsForResource" ], "Resource": "*" } ] } ``` 4. Optional: create an event rule to trigger automatic syncing of changes in Elasticache serverless caches. You may use the following CloudFormation template: **Event rule CloudFormation template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: ElasticacheEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AWS API Call via CloudTrail source: - aws.elasticache detail: eventSource: - elasticache.amazonaws.com eventName: - prefix: CreateServerlessCache - prefix: DeleteServerlessCache - prefix: ModifyServerlessCache Name: port-aws-exporter-sync-serverless-cache-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName cacheName: $.detail.responseElements.serverlessCache.serverlessCacheName InputTemplate: >- { "resource_type": "AWS::ElastiCache::ServerlessCache", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" | startswith(\"DeleteServerlessCache\") then \"delete\" else \"upsert\" end" } ElasticacheTagRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticache detail-type: - AWS API Call via CloudTrail detail: eventSource: - elasticache.amazonaws.com eventName: - prefix: AddTagsToResource - prefix: RemoveTagsFromResource Name: port-aws-exporter-sync-serverless-cache-tags-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName resourceArn: $.detail.requestParameters.resourceName InputTemplate: |- { "resource_type": "AWS::ElastiCache::ServerlessCache", "region": "\"\"", "identifier": "\"\" | split(\":\") | .[-1]", "action": "\"upsert\"" } ``` Done! soon, you will be able to see any `Serverless cache` ## Elasticache cluster[​](#elasticache-cluster "Direct link to Elasticache cluster") In this step-by-step example, you will export your `Cache clusters` to Port. 1. Create the following Port blueprint: **Cache cluster** - will represent cache clusters from the AWS account. You may use the following definitions: **Cache cluster blueprint (click to expand)** Create in Port ``` { "identifier": "elasticache_cluster", "title": "Elasticache Cluster", "icon": "Service", "schema": { "properties": { "engine": { "title": "Engine", "type": "string" }, "engineVersion": { "title": "Engine Version", "type": "string" }, "preferredAvailabilityZone": { "title": "Preferred Availability Zone", "type": "string" }, "createdDate": { "title": "Created Date", "type": "string", "format": "date-time" }, "arn": { "title": "ARN", "type": "string" }, "transitEncryptionEnabled": { "title": "Transit Encryption Enabled", "type": "boolean" }, "atRestEncryptionEnabled": { "title": "At Rest Encryption Enabled", "type": "boolean" }, "nodeType": { "icon": "DefaultProperty", "title": "Node Type", "type": "string" }, "status": { "title": "Cache Cluster Status", "type": "string" }, "numNodes": { "title": "Num Nodes", "type": "number" }, "securityGroups": { "title": "Security Groups", "type": "array" }, "subnetGroupName": { "title": "Subnet Group Name", "type": "string" }, "link": { "title": "Link", "type": "string", "format": "url" }, "tags": { "title": "Tags", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "region": { "title": "Region", "target": "region", "required": false, "many": false } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::ElastiCache::CacheCluster", "port": { "entity": { "mappings": [ { "identifier": ".CacheClusterId", "title": ".CacheClusterId", "blueprint": "aws_cache_cluster", "properties": { "engine": ".Engine", "engineVersion": ".EngineVersion", "preferredAvailabilityZone": ".PreferredAvailabilityZone", "createdDate": ".CacheClusterCreateTime", "arn": ".ARN", "transitEncryptionEnabled": ".TransitEncryptionEnabled", "atRestEncryptionEnabled": ".AtRestEncryptionEnabled", "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .ARN", "nodeType": ".CacheNodeType", "status": ".CacheClusterStatus", "tags": ".Tags", "numNodes": ".NumCacheNodes", "securityGroups": ".CacheSecurityGroups", "subnetGroupName": ".CacheSubnetGroupName" }, "relations": { "region": ".ARN | split(\":\") | .[3]" } } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "elasticache:DescribeCacheClusters", "elasticache:ListTagsForResource" ], "Resource": "*" } ] } ``` 4. Optional: create an event rule to trigger automatic syncing of changes in Elasticache clusters. You may use the following CloudFormation template: **Event rule CloudFormation template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: ElasticacheEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AWS API Call via CloudTrail source: - aws.elasticache detail: eventSource: - elasticache.amazonaws.com eventName: - prefix: CreateCacheCluster - prefix: DeleteCacheCluster - prefix: ModifyCacheCluster Name: port-aws-exporter-sync-cache-cluster-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName cacheName: $.detail.responseElements.cacheClusterId InputTemplate: >- { "resource_type": "AWS::ElastiCache::CacheCluster", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" | startswith(\"DeleteCacheCluster\") then \"delete\" else \"upsert\" end" } ElasticacheTagRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.elasticache detail-type: - AWS API Call via CloudTrail detail: eventSource: - elasticache.amazonaws.com eventName: - prefix: AddTagsToResource - prefix: RemoveTagsFromResource Name: port-aws-exporter-sync-cache-cluster-tags-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName resourceArn: $.detail.requestParameters.resourceName InputTemplate: |- { "resource_type": "AWS::ElastiCache::CacheCluster", "region": "\"\"", "identifier": "\"\" | split(\":\") | .[-1]", "action": "\"upsert\"" } ``` Done! soon, you will be able to see any `Cache clusters` ## Auto scaling group[​](#auto-scaling-group "Direct link to Auto scaling group") In this step-by-step example, you will export your EC2 `Auto scaling groups` to Port. 1. Create the following Port blueprint: **Auto scaling group** - will represent EC2 auto scaling group from the AWS account. You may use the following definitions: **Auto scaling group blueprint (click to expand)** Create in Port ``` { "identifier": "awsAutoScalingGroup", "title": "Auto Scaling Group", "icon": "AWS", "schema": { "properties": { "availabilityZones": { "type": "array", "title": "Availability Zones" }, "desiredCapacity": { "type": "number", "title": "Desired Capacity" }, "healthCheckType": { "type": "string", "title": "Health Check Type" }, "loadBalancerNames": { "type": "array", "title": "Load Balancer Names" }, "maximumCapacity": { "type": "number", "title": "Maximum Capacity" }, "minimumCapacity": { "type": "number", "title": "Minimum Capacity" }, "serviceRoleArn": { "type": "string", "title": "Service Role ARN" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "region": { "title": "Region", "target": "region", "required": false, "many": false } } } ``` 2. Upload the `config.json` file to the exporter's S3 bucket: **Port AWS exporter config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::AutoScaling::AutoScalingGroup", "port": { "entity": { "mappings": [ { "identifier": ".AutoScalingGroupName", "title": ".AutoScalingGroupName", "blueprint": "awsAutoScalingGroup", "properties": { "tags": ".Tags", "desiredCapacity": ".DesiredCapacity", "minimumCapacity": ".MinSize", "maximumCapacity": ".MaxSize", "availabilityZones": ".AvailabilityZones", "loadBalancerNames": ".LoadBalancerNames", "serviceRoleArn": ".ServiceLinkedRoleARN", "healthCheckType": ".HealthCheckType" }, "relations": {} } ] } } } ] } ``` 3. Update the exporter's `IAM policy`: **IAM policy (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": ["autoscaling:Describe*"], "Resource": "*" } ] } ``` 4. Optional: create an event rule to trigger automatic syncing of changes in Auto scaling groups. You may use the following CloudFormation template: **Event rule CloudFormation template (click to expand)** ``` AWSTemplateFormatVersion: '2010-09-09' Description: The template used to create event rules for the Port AWS exporter. Parameters: PortAWSExporterStackName: Description: Name of the Port AWS exporter stack name Type: String MinLength: 1 MaxLength: 255 AllowedPattern: ^[a-zA-Z][-a-zA-Z0-9]*$ Default: serverlessrepo-port-aws-exporter Resources: AutoScalingGroupEventRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: detail-type: - AWS API Call via CloudTrail source: - aws.autoscaling detail: eventSource: - autoscaling.amazonaws.com eventName: - prefix: CreateAutoScalingGroup - prefix: DeleteAutoScalingGroup - prefix: UpdateAutoScalingGroup Name: port-aws-exporter-sync-auto-scaling-group-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName autoScalingGroupName: $.detail.requestParameters.autoScalingGroupName InputTemplate: >- { "resource_type": "AWS::AutoScaling::AutoScalingGroup", "region": "\"\"", "identifier": "\"\"", "action": "if \"\" | startswith(\"DeleteAutoScalingGroup\") then \"delete\" else \"upsert\" end" } AutoScalingGroupTagRule: Type: AWS::Events::Rule Properties: EventBusName: default EventPattern: source: - aws.autoscaling detail-type: - AWS API Call via CloudTrail detail: eventSource: - autoscaling.amazonaws.com eventName: - prefix: CreateOrUpdateTags - prefix: DeleteTags Name: port-aws-exporter-sync-auto-scaling-group-tags-trails State: ENABLED Targets: - Id: PortAWSExporterEventsQueue Arn: Fn::ImportValue: Fn::Sub: ${PortAWSExporterStackName}-EventsQueueARN InputTransformer: InputPathsMap: awsRegion: $.detail.awsRegion eventName: $.detail.eventName resourceId: $.detail.requestParameters.tags[0].resourceId InputTemplate: |- { "resource_type": "AWS::AutoScaling::AutoScalingGroup", "region": "\"\"", "identifier": "\"\"", "action": "\"upsert\"" } ``` Done! soon, you will be able to see any `Auto scaling groups` ## Connect Cloud Resources to Services.[​](#connect-cloud-resources-to-services "Direct link to Connect Cloud Resources to Services.") This guide demonstrates how to connect a Cloud Resource to a service or list of services by relying on the tags property and using JQ mapping. We will be using an S3 bucket as the Cloud Resource. Prerequisite This guide assumes that you have ingested S3 buckets into Port using the `terraform` option of the [AWS exporter](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/installation/.md#terraform-installation-recommended). ### Ingest Services from GitHub.[​](#ingest-services-from-github "Direct link to Ingest Services from GitHub.") Follow this [guide](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/#setup) to ingest your GitHub repositories into Port. Port will create a `service` blueprint that you will be using subsequently. **Service Blueprint (click to expand)** Create in Port ``` { "identifier": "service", "title": "Service", "icon": "Github", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown", "icon": "Book" }, "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link" }, "language": { "type": "string", "title": "Language", "icon": "Git" }, "slack": { "icon": "Slack", "type": "string", "title": "Slack", "format": "url" }, "tier": { "title": "Tier", "type": "string", "description": "How mission-critical the service is", "enum": [ "Mission Critical", "Customer Facing", "Internal Service", "Other" ], "enumColors": { "Mission Critical": "turquoise", "Customer Facing": "green", "Internal Service": "darkGray", "Other": "yellow" }, "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Your [data model](https://app.port.io/settings/data-model) should now contain the `Service` blueprint: ![S3 Bucket and Service blueprints](/img/guides/connectCloudServiceBlueprints.png) ### Create the relation[​](#create-the-relation "Direct link to Create the relation") Now that Port is synced with your `s3_bucket` and `service` blueprints, let's map the two together. 1. Add a tag to the bucket with the key `service` and a value representing the identifier of the service. For instance, if your service has an identifier of `webapp`, create a tag on the bucket with `{ "service": "webapp" }`. Refer to this [AWS guide](https://docs.aws.amazon.com/AmazonS3/latest/userguide/tagging-managing.html) for more details on tagging resources in S3. ![S3 bucket tagging form with service tags](/img/guides/connectCloudServiceTagging.png)

2. In the [Port AWS exporter repository](https://github.com/port-labs/template-assets) you cloned during the [ingestion step](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/installation/.md#terraform-installation-recommended), go to the `template-assets/aws/s3_bucket` folder. We are going to add a relation from our S3 bucket configuration to the `Service` blueprint by editing two files: * `blueprint.tf`: We will define the relation to `service` in the S3 bucket blueprint. * `config.json`: We then write the mapping logic to define how a service is linked to a bucket based on tags.
**S3 Bucket Blueprint (click to expand)** blueprint.tf ``` terraform { required_providers { port-labs = { source = "port-labs/port-labs" version = "2.0.3" } } } resource "port-labs_blueprint" "s3_bucket" { title = "S3 Bucket" icon = "Bucket" identifier = "s3_bucket" // ... other properties properties { identifier = "tags" type = "array" title = "Tags" } relations { target = "service" title = "Consuming Service" identifier = "consumingService" many = false required = false } } ``` **Mapping Logic (click to expand)** config.json ``` { "kind": "AWS::S3::Bucket", "port": { "entity": { "mappings": [ { "identifier": ".BucketName", "title": ".BucketName", "blueprint": "s3_bucket", "properties": { "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "regionalDomainName": ".RegionalDomainName", "versioningStatus": ".VersioningConfiguration.Status", "encryption": ".BucketEncryption.ServerSideEncryptionConfiguration", "lifecycleRules": ".LifecycleConfiguration.Rules", "publicAccess": ".PublicAccessBlockConfiguration", "tags": ".Tags", "arn": ".Arn" }, "relations": { "consumingService": ".Tags[] | select(.Key == \"service\") | .Value" } } ] } } } ```
3. Run terraform again to apply the changes ``` # Run the aws exporter again. terraform apply -var 'resources=["s3_bucket"]' ``` ### View the relation[​](#view-the-relation "Direct link to View the relation") You can view the relation in your [data model](https://app.port.io/settings/data-model) ![S3 Bucket related to Service blueprint](/img/guides/connectCloudServiceRelation.png)

Now go to your [catalog](https://app.port.io/s3_buckets), and click on the S3 bucket you used in this guide. You will see it connected to the service. ![S3 bucket entity with webapp service relation](/img/guides/connectCloudServiceFinal.png)

Done! 🎉 You can replicate this for all your other resources. --- # Connect ECR repository to a service This guide aims to show how to connect an ECR `repository` to an existing service in Port. Prerequisites This guide assumes you have: * A Port account and that you have completed the [onboarding process](/getting-started/overview/.md). We will use the `Service` blueprint created during the onboarding process. * [AWS exporter installed and configured](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md) in your environment. ## Add tags to repositories in AWS ECR[​](#add-tags-to-repositories-in-aws-ecr "Direct link to Add tags to repositories in AWS ECR") Tagging repositories in ECR allows you to categorize and filter them. You can use tags to specify a repository that is related to a specific service in Port. In this guide, we will add a tag to tell us what service the repository is related to. To tag an ECR repository, you can use either the AWS CLI or the ECR console. Here are the basic steps: ### Using the ECR console[​](#using-the-ecr-console "Direct link to Using the ECR console") 1. **Open the Amazon ECR console** and **select the [region](https://console.aws.amazon.com/console/home)**. 2. **Choose "Repositories"** and select the repository you want to tag. 3. **Select "Repository tags"** from the actions menu on the left side. 4. **Choose "Add tags"** and **specify the key-value pairs** for each tag. ### Using the AWS CLI[​](#using-the-aws-cli "Direct link to Using the AWS CLI") Run the command: ``` aws ecr tag-resource \ --resource-arn arn:aws:ecr:region:account-id:repository/repository-name \ --tags Key=tag-key,Value=tag-value ``` This will add a tag with the specified key-value pair to the repository. You can add multiple tags by specifying additional `--tags` parameters. Control the tag key-value pair Since our ECR `repository` may already have several tags, we will need a mechanism to control how these tags will be related to our `Service` blueprint. A way to achieve this relation is to prefix the tag key with the keyword `port-` and the value would be anything. We will then use JQ to select the keys that starts with this keyword. For this guide, our tag key will be named `port-auth-service`, which will correspond to a `Service` entity identified by `auth-service` in Port. Since tag values can be optional, we can leave it blank; only the keys matter to us. ## Create the service relation[​](#create-the-service-relation "Direct link to Create the service relation") With our ECR resources synced with Port, let's reflect the ECR Repository in our services to display the repository associated with a service. First, we will need to create a [relation](/context-lake/data-model/setup-blueprint/relate-blueprints/.md) between our services and the corresponding ECR Repository. To do this: 1. Go back to the [Data model](https://app.port.io/settings/data-model) page in Port, choose the `ECR Repository` [blueprint](), and click on **New relation**. 2. Fill out the form with the following values, then click **Create**: * **Title**: `Service`. * **Identifier**: `service` (auto-generated from title). * **Target blueprint**: `Service`. * **Required**: leave unchecked. * **Many**: leave unchecked. With the [blueprints]() related, we need to assign the relevant ECR Repository to each of our services. This is done by adding some mapping logic. Update the [`config.json`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#exporter-configjson-file) file from the AWS exporter to: **config.json (click to expand)** ``` { "resources": [ { "kind": "AWS::ECR::Repository", "port": { "entity": { "mappings": [ { "identifier": ".RepositoryName", "title": ".RepositoryName", "blueprint": "ecr_repository", "properties": { "imageTagMutability": ".ImageTagMutability", "scanningConfiguration": ".ImageScanningConfiguration", "repositoryArn": ".Arn", "link": "\"https://console.aws.amazon.com/go/view?arn=\" + .Arn", "repositoryUri": ".RepositoryUri", "encryptionConfiguration": ".EncryptionConfiguration", "lifecyclePolicy": ".LifecyclePolicy", "tags": ".Tags" }, "relations": { "region": ".Arn | split(\":\") | .[3]", "service": ".Tags | keys[] | select(startswith(\"port-\")) | .[6:]" } } ] } } } ] } ``` JQ explanation The JQ below selects all keys that start with the keyword `port-`. It then removes "port-" from each key, leaving only the part that comes after it and goes ahead to select all keys that fits this criteria as an array. ``` service: .Tags | keys[] | select(startswith("port-")) | .[6:] ``` What we just did was map the `ECR Repository` to the relation between it and our `Services`. Now, if our `Service` identifier is equal to the ECR Repository tag key, the `service` will automatically be linked to it. ## Conclusion[​](#conclusion "Direct link to Conclusion") By following these steps, you can seamlessly connect an ECR repository with an existing service blueprint in Port using tags. Relevant guides and examples: * [Port's AWS integration for ECR Repositories](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/examples#ecr-repositories). * [Python script to ingest ECR Repositories and Images into Port](https://github.com/port-labs/example-ecr-images). --- # Installation First Time Installation For your first deployment of the AWS exporter, we recommend starting with the Helm/scheduled installation method to perform the initial data sync. Once the initial data sync is complete, you can switch to the Terraform deployment method for real-time data sync. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * You will need your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) to install the AWS exporter: **Get your Port credentials (click to expand)** To get your Port credentials, open the [Credentials](https://app.port.io/organization/home?openModal=credentials) modal. Here you can view and copy your `CLIENT_ID` and `CLIENT_SECRET`: ![Port Credentials modal showing organization, client ID, client secret, and API token controls](/img/software-catalog/credentials-modal.png) * The [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) is required for authentication. Make sure your AWS `Access key id` and `Secret access key` are set. If not, run `aws configure` in your terminal to configure them. For the [step-by-step installation](#step-by-step-installation) (not using Terraform), also install: * [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) * [JQ](https://stedolan.github.io/jq/download/) ## Terraform Installation (recommended)[​](#terraform-installation-recommended "Direct link to Terraform Installation (recommended)") Run the following script in your terminal: ``` # Export your Port credentials export PORT_CLIENT_ID=YOUR-PORT-CLIENT-ID export PORT_CLIENT_SECRET=YOUR-PORT-CLIENT-SECRET # Clone the terraform template git clone https://github.com/port-labs/template-assets.git cd template-assets/aws # Initialize the Terraform requirements terraform init # Deploy the aws exporter and provide the resources you want to export terraform apply -var 'resources=["ecs_service", "lambda", "sns", "sqs", "s3_bucket", "rds_db_instance", "dynamodb_table", "ec2_instance"]' ``` About the script The above script performs the following actions: 1. Creates the resource blueprints in your Port environment. 2. Deploys the AWS exporter in your AWS environment with the following resources - [S3 bucket](https://aws.amazon.com/s3/), [mapping configuration file](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#exporter-configjson-file), [AWS secret](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#port-credentials-secret), [AWS IAM policy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#iam-policy). 3. Setting up [Event Bridge Rules](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rules.html) that trigger the exporter to update resources. 4. Invokes the AWS exporter Lambda function for the first time to get the current resources state. You can delete resources you don't want to export by removing them from the resources array in the script above. ## Step-by-step installation[​](#step-by-step-installation "Direct link to Step-by-step installation") The steps outlined here can be used to manually install the AWS exporter using CloudFormation. In order to deploy the application, you will need to fill in the following parameters: * **Cloud Formation related parameters:** * `Application name` - The stack name of the application created via `AWS CloudFormation`. * **Bucket related parameters:** * `CreateBucket` - `true` if you want the application to create and manage your bucket, or `false` if you want to create the bucket on your own. * `BucketName` - The name of your bucket, or a globally unique name for a new bucket. * `ConfigJsonFileKey` - The file key (path) to the [`config.json`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#exporter-configjson-file) in the bucket. * **IAM Policy related parameters:** * `CustomIAMPolicyARN` - The ARN of the [IAM policy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#iam-policy). * **Secret related parameters:** * `CustomPortCredentialsSecretARN` - The ARN of the Port credentials secret. **OR** * `SecretName` - The name for the new Port credentials secret to create. * **Lambda related parameters:** * `FunctionName` - The function name for the exporter's lambda. * `ScheduleExpression` - The [schedule expression](https://docs.aws.amazon.com/lambda/latest/dg/services-cloudwatchevents-expressions.html) to define an event schedule for the exporter. * `ScheduleState` - The schedule initial state - `ENABLED` or `DISABLED`. We recommend to enable it only after one successful run. 1. Prepare a [`config.json`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/.md#exporter-configjson-file) file that will define which AWS resources to ingest to Port. 2. Create the [`IAM policy`](#iam-policy) that provides permissions to `list` and `read` the AWS resources in the `config.json`. Create a policy An IAM policy reference is available [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html). 3. Deploy our [`serverless application`](#exporter-aws-serverless-application). * AWS Console * AWS CLI You can deploy the application from the AWS console through this [link](https://eu-west-1.console.aws.amazon.com/lambda/home?region=eu-west-1#/create/app?applicationId=arn:aws:serverlessrepo:eu-west-1:185657066287:applications/port-aws-exporter). Follow these steps: 1. Create a `parameters.json` file to override certain parameters values. For example (replace the placeholders): ``` [ { "Name": "CustomIAMPolicyARN", "Value": "" }, { "Name": "CustomPortCredentialsSecretARN", "Value": "" }, { "Name": "CreateBucket", "Value": "false" }, { "Name": "BucketName", "Value": "" }, { "Name": "ScheduleExpression", "Value": "rate(1 hour)" }, { "Name": "ScheduleState", "Value": "DISABLED" } ] ``` 2. Use the following command to create a change set: ``` aws serverlessrepo create-cloud-formation-change-set --application-id arn:aws:serverlessrepo:eu-west-1:185657066287:applications/port-aws-exporter --stack-name port-aws-exporter --capabilities CAPABILITY_IAM CAPABILITY_RESOURCE_POLICY --parameter-overrides file://parameters.json # Result { "ApplicationId": "arn:aws:serverlessrepo:eu-west-1:185657066287:applications/port-aws-exporter", "ChangeSetId": "", ... } ``` 3. With the `` from the previous command output, deploy the change set: ``` aws cloudformation execute-change-set --change-set-name "" ``` List the exporter's resources After the deployment is complete, use the following AWS SAM CLI command to get a useful list of the exporter's resources: ``` sam list stack-outputs --stack-name serverlessrepo-port-aws-exporter ``` The list includes: * `Lambda Function ARN` - the ARN of the exporter's Lambda. * `Port Credentials Secret ARN` - the ARN of the Port credentials secret. * `ConfigBucketName` - the exporter's bucket name. For more information regarding how to deploy a serverless application, click [here](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/serverlessrepo-how-to-consume.html). 4. Update the [`Port credentials secret`](#port-credentials-secret) with your credentials. Modify a secret To learn how to modify a secret's value, look [here](https://docs.aws.amazon.com/secretsmanager/latest/userguide/manage_update-secret.html). 5. Upload the `config.json` to the exporter's S3 bucket. Upload a file to an S3 bucket To learn how to upload a file to S3, look [here](https://docs.aws.amazon.com/AmazonS3/latest/userguide/upload-objects.html). ### Test the application[​](#test-the-application "Direct link to Test the application") In order to test the deployed application, you should run the exporter's lambda with an empty test event (`{}`), and review the execution status and logs. Invoke a function with a test event A reference showing how to invoke a lambda function with a test event can be found [here](https://docs.aws.amazon.com/lambda/latest/dg/testing-functions.html#invoke-with-event). The exporter's lambda can run for more than 15 minutes (the maximum amount of time that a Lambda function can run). If a function has been running for more than 10 minutes, and there are any resources left to sync, a new lambda instance will be launched to continue the syncing process. ### Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") #### View the logs[​](#view-the-logs "Direct link to View the logs") To view the logs of all the lambda instances in one place, you can use [Cloudwatch Logs](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-console) or [AWS SAM Logs](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html): ``` sam logs --stack-name serverlessrepo-port-aws-exporter --tail ``` ### Update the schedule settings[​](#update-the-schedule-settings "Direct link to Update the schedule settings") After running the exporter successfully for the first time, you probably want to alter the scheduling related properties of the CloudFormation stack: * `ScheduleExpression` - Make sure to set an interval that is longer than the time it takes for the exporter to execute. * `ScheduleState` - Set the schedule state to `ENABLED`. If you are using the Terrafom module, update the `schedule_state` and `schedule_expression` variables. Determine lambda execution time In order to determine lambda execution time, you can [view the logs](#view-the-logs), and search for the first and last log lines. When the exporter finishes its syncing work, it writes the following log: `Done handling your resources`. Updating an application's setting or version is done using the same procedure as deploying a new application, similar to step 3 of the [installation](#installation). By default, the latest available version of the exporter will be used when you run the update/deploy procedure. click [here](https://docs.aws.amazon.com/serverlessrepo/latest/devguide/serverlessrepo-how-to-consume-new-version.html) to learn more. ### Configure the AWS Exporter to run on events[​](#configure-the-aws-exporter-to-run-on-events "Direct link to Configure the AWS Exporter to run on events") In addition to running on schedule, the AWS exporter can be used to act on live events, such as create, update and delete of a resource in your AWS account. That way you can configure a resource to be synced as soon as it changed, in real time. To configure the AWS exporter to use events as triggers, follow these steps: 1. Prepare an [event rule](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/event-based-updates/.md#definition), based on specific events matching resources you want the AWS exporter to update in real time and save it to a Cloudformation YAML template (`template.yml`). 2. Deploy the event rule using this command: ``` aws cloudformation deploy --template-file template.yml --stack-name port-aws-exporter-event-rules ``` ## Further information[​](#further-information "Direct link to Further information") * Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/examples/.md) page for practical configurations and their corresponding blueprint definitions. * Learn about more ways of [working with Cloudformation stacks](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacks.html). * Deep-dive into the [Event-based Updates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/aws-exporter/event-based-updates/.md) mechanism. --- # Examples This page contains the base examples for mapping AWS resources to Port. This base example thrives to provide a simpler and more abstract way to map AWS resources to Port. The simplification is achieved by using the generic `cloudResource` blueprint, which can be used to map any AWS resource to Port. ## Mapping AWS Accounts[​](#mapping-aws-accounts "Direct link to Mapping AWS Accounts") The following example demonstrates how to ingest your AWS Accounts to Port.
You can use the following Port blueprint definitions and integration configuration: **Account Blueprint (click to expand)** Create in Port ``` { "identifier": "awsAccount", "title": "AWS account", "icon": "AWS", "schema": { "properties": { "arn": { "type": "string", "title": "Arn" }, "email": { "type": "string", "title": "Email" }, "status": { "type": "array", "title": "Status", "default": [ "ACTIVE" ], "items": { "enum": [ "ACTIVE", "SUSPENDED", "PENDING_CLOSURE" ], "enumColors": { "ACTIVE": "green", "SUSPENDED": "red", "PENDING_CLOSURE": "yellow" }, "type": "string" } }, "joined_method": { "type": "string", "title": "Joined Method", "enum": [ "INVITED", "CREATED" ], "enumColors": { "INVITED": "lightGray", "CREATED": "lightGray" } }, "joined_timestamp": { "type": "string", "title": "Joined Timestamp", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} }, ``` **Mapping configuration for AWS Accounts (click to expand)** ``` resources: - kind: AWS::Organizations::Account selector: query: 'true' port: entity: mappings: identifier: .Id title: .Name blueprint: '"awsAccount"' properties: arn: .Arn email: .Email status: .Status joined_method: .JoinedMethod joined_timestamp: .JoinedTimestamp ``` Here are the API references we used to create those blueprints and app config: * [Accounts](https://docs.aws.amazon.com/cli/latest/reference/organizations/list-accounts.html) ## Mapping Cloud Resources[​](#mapping-cloud-resources "Direct link to Mapping Cloud Resources") The following example demonstrates how to ingest your AWS Resources to Port.
You can use the following Port blueprint definitions and integration configuration: Relate resources and Accounts The Resources below have a relation to the Account, so the creation of the [Account](#mapping-gcp-projects) is required. **Cloud Resource Blueprint (click to expand)** Create in Port ``` { "identifier": "cloudResource", "title": "AWS Cloud Resource", "icon": "AWS", "schema": { "properties": { "kind": { "title": "Kind", "type": "string" }, "tags": { "items": { "type": "object" }, "type": "array", "title": "Tags" }, "arn": { "type": "string", "title": "ARN" }, "link": { "type": "string", "title": "Link", "icon": "AWS", "format": "url" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Mapping configuration for cloud resources (click to expand)** ``` resources: - kind: AWS::S3::Bucket selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .__Region tags: .Properties.Tags arn: .Properties.Arn link: >- .Properties | select(.Arn != null) | "https://console.aws.amazon.com/go/view?arn=" + .Arn relations: account: .__AccountId - kind: AWS::EC2::Instance selector: query: 'true' port: entity: mappings: identifier: .Identifier title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .__Region tags: .Properties.Tags arn: >- 'arn:aws:ec2:' + .__Region + ':' + .__AccountId + ':instance/' + .Properties.InstanceId link: >- 'https://console.aws.amazon.com/ec2/home?region=' + .__Region + InstanceDetails:instanceId=' + Properties.InstanceId relations: account: .__AccountId - kind: AWS::ECS::Cluster selector: query: 'true' port: entity: mappings: identifier: .Identifier title: .Identifier blueprint: '"cloudResource"' properties: kind: .__Kind region: .__Region tags: .Properties.Tags arn: .Properties.Arn link: >- .Properties | select(.Arn != null) | "https://console.aws.amazon.com/go/view?arn=" + .Arn relations: account: .__AccountId ``` ## Mapping Extra Resources[​](#mapping-extra-resources "Direct link to Mapping Extra Resources") The resources in this page are only a few of the resources that the AWS Integration supports. If the resources you want to ingest into Port do not appear in these examples, you can head to the [Mapping Extra Resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/examples/mapping_extra_resources/.md) page to learn about all of the kinds of AWS resources that are supported by the AWS integration and how to map them into Port. --- # Mapping extra resources As you've probably looked at the [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/examples/.md) page, you've noticed that the AWS Integration supports some AWS resources, but most of them are not documented in the Examples page. This page will help you understand what kind of AWS resources are supported by the AWS integration and how to map them into Port. ## Is the resource supported by the AWS Integration?[​](#is-the-resource-supported-by-the-aws-integration "Direct link to Is the resource supported by the AWS Integration?") The AWS Integration relies on AWS's Cloud Control API. That means: 1. Is the type of resource I want to ingest listed [here](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html) and supported by the list method? * **If so**: Great! It's supported. * **If not**: Please contact us or contribute by [adding support](https://github.com/port-labs/ocean/blob/main/CONTRIBUTING.md) to [the integration](https://github.com/port-labs/ocean/tree/main/integrations/aws) yourself. For the full list of supported resources, refer to [AWS Cloud Control API Supported resources](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html). ## Unsupported resources[​](#unsupported-resources "Direct link to Unsupported resources") The AWS integration relies on AWS's Cloud Control API. While many resources are supported, some require additional input parameters to be queried, which the integration currently does not support. ### Commonly requested unsupported services[​](#commonly-requested-unsupported-services "Direct link to Commonly requested unsupported services") The following high-demand services are frequently requested but cannot be supported through the Cloud Control API: | Service | Resource types | Why unsupported | | ------------------ | ------------------------------------------ | ---------------------------------------------------------------- | | **Step Functions** | `StateMachineAlias`, `StateMachineVersion` | Requires parent state machine ARN to list versions/aliases. | | **EventBridge** | `Rule`, `Archive` | Rules require event bus name, archives require event source ARN. | | **Glue** | `SchemaVersion`, `SchemaVersionMetadata` | Requires schema registry and schema identifiers. | | **DMS** | Database Migration Service resources | Not exposed through Cloud Control API. | | **Lambda** | `Alias`, `Version`, `Permission`, `Url` | Requires parent function name to list sub-resources. | | **EKS** | `Addon`, `Nodegroup`, `FargateProfile` | Requires cluster name to list cluster-scoped resources. | | **API Gateway** | Stages, deployments, authorizers, models | Requires parent API ID to list sub-resources. | ### Full list of unsupported resources[​](#full-list-of-unsupported-resources "Direct link to Full list of unsupported resources") **AWS resources that requires additional input (click to expand)** ``` AWS::Amplify::Branch AWS::AmplifyUIBuilder::Component AWS::AmplifyUIBuilder::Form AWS::AmplifyUIBuilder::Theme AWS::ApiGateway::Authorizer AWS::ApiGateway::BasePathMapping AWS::ApiGateway::DocumentationPart AWS::ApiGateway::Deployment AWS::ApiGateway::DocumentationVersion AWS::ApiGateway::GatewayResponse AWS::ApiGateway::Model AWS::ApiGateway::RequestValidator AWS::ApiGateway::Resource AWS::ApiGateway::Stage AWS::ApiGateway::UsagePlanKey AWS::ApiGatewayV2::Authorizer AWS::ApiGatewayV2::ApiMapping AWS::ApiGatewayV2::Deployment AWS::ApiGatewayV2::Model AWS::ApiGatewayV2::Integration AWS::ApiGatewayV2::Route AWS::AppConfig::ConfigurationProfile AWS::AppConfig::Environment AWS::AppConfig::HostedConfigurationVersion AWS::ApplicationAutoScaling::ScalingPolicy AWS::AppSync::FunctionConfiguration AWS::AppSync::Resolver AWS::AppSync::SourceApiAssociation AWS::APS::RuleGroupsNamespace AWS::Athena::PreparedStatement AWS::Bedrock::DataSource AWS::Bedrock::AgentAlias AWS::Bedrock::FlowAlias AWS::Bedrock::FlowVersion AWS::Bedrock::PromptVersion AWS::CleanRooms::AnalysisTemplate AWS::CleanRooms::ConfiguredTableAssociation AWS::CleanRooms::IdMappingTable AWS::CleanRooms::IdNamespaceAssociation AWS::CleanRooms::PrivacyBudgetTemplate AWS::CloudFormation::ResourceVersion AWS::CodeArtifact::PackageGroup AWS::Cognito::IdentityPoolPrincipalTag AWS::Cognito::IdentityPoolRoleAttachment AWS::Cognito::UserPoolClient AWS::Cognito::UserPoolGroup AWS::Cognito::UserPoolResourceServer AWS::Cognito::UserPoolUser AWS::ControlTower::EnabledControl AWS::CustomerProfiles::EventStream AWS::CustomerProfiles::Integration AWS::CustomerProfiles::ObjectType AWS::DataZone::DataSource AWS::DataZone::Environment AWS::DataZone::EnvironmentActions AWS::DataZone::EnvironmentBlueprintConfiguration AWS::DataZone::EnvironmentProfile AWS::DataZone::GroupProfile AWS::DataZone::Project AWS::DataZone::ProjectMembership AWS::DataZone::SubscriptionTarget AWS::DataZone::UserProfile AWS::Deadline::Fleet AWS::Deadline::MeteredProduct AWS::Deadline::Queue AWS::Deadline::QueueEnvironment AWS::Deadline::QueueFleetAssociation AWS::Deadline::StorageProfile AWS::EC2::IPAMAllocation AWS::EC2::IPAMPoolCidr AWS::EC2::Route AWS::EC2::TransitGatewayMulticastDomainAssociation AWS::EC2::TransitGatewayMulticastGroupMember AWS::EC2::TransitGatewayMulticastGroupSource AWS::EC2::TransitGatewayRoute AWS::EC2::TransitGatewayRouteTableAssociation AWS::EC2::TransitGatewayRouteTablePropagation AWS::EC2::VPCCidrBlock AWS::ECS::TaskSet AWS::EFS::MountTarget AWS::EKS::AccessEntry AWS::EKS::Addon AWS::EKS::FargateProfile AWS::EKS::IdentityProviderConfig AWS::EKS::Nodegroup AWS::EKS::PodIdentityAssociation AWS::ElasticLoadBalancingV2::Listener AWS::ElasticLoadBalancingV2::ListenerRule AWS::ElasticLoadBalancingV2::TrustStoreRevocation AWS::EntityResolution::PolicyStatement AWS::EventSchemas::Schema AWS::FIS::TargetAccountConfiguration AWS::Glue::SchemaVersion AWS::Glue::SchemaVersionMetadata AWS::GreengrassV2::ComponentVersion AWS::IdentityStore::Group AWS::IdentityStore::GroupMembership AWS::ImageBuilder::Component AWS::ImageBuilder::Image AWS::ImageBuilder::Workflow AWS::IoTSiteWise::AccessPolicy AWS::IoTSiteWise::Dashboard AWS::IoTTwinMaker::ComponentType AWS::IoTTwinMaker::Entity AWS::IoTTwinMaker::SyncJob AWS::IoTTwinMaker::Scene AWS::IVS::StreamKey AWS::Kendra::DataSource AWS::Kendra::Faq AWS::Lambda::Alias AWS::Lambda::EventInvokeConfig AWS::Lambda::Permission AWS::Lambda::Url AWS::Lambda::Version AWS::Logs::AccountPolicy AWS::Logs::LogStream AWS::Logs::SubscriptionFilter AWS::MediaConnect::FlowEntitlement AWS::MediaConnect::FlowOutput AWS::MediaConnect::FlowSource AWS::MediaConnect::FlowVpcInterface AWS::MediaLive::ChannelPlacementGroup AWS::MediaLive::Multiplexprogram AWS::MediaPackage::Asset AWS::MediaPackage::PackagingConfiguration AWS::MediaPackageV2::Channel AWS::MediaPackageV2::OriginEndpoint AWS::MediaTailor::LiveSource AWS::MediaTailor::VodSource AWS::MSK::BatchScramSecret AWS::MSK::ClusterPolicy AWS::NetworkFirewall::LoggingConfiguration AWS::NetworkManager::CustomerGatewayAssociation AWS::NetworkManager::Device AWS::NetworkManager::Link AWS::NetworkManager::LinkAssociation AWS::NetworkManager::Site AWS::NetworkManager::TransitGatewayRegistration AWS::OpenSearchServerless::AccessPolicy AWS::OpenSearchServerless::LifecyclePolicy AWS::OpenSearchServerless::SecurityConfig AWS::OpenSearchServerless::SecurityPolicy AWS::Organizations::OrganizationalUnit AWS::Organizations::Policy AWS::PCAConnectorAD::ServicePrincipalName AWS::PCAConnectorAD::Template AWS::PCAConnectorAD::TemplateGroupAccessControlEntry AWS::PCAConnectorSCEP::Challenge AWS::QLDB::Stream AWS::QuickSight::Analysis AWS::QuickSight::Dashboard AWS::QuickSight::DataSet AWS::QuickSight::DataSource AWS::QuickSight::RefreshSchedule AWS::QuickSight::Template AWS::QuickSight::Theme AWS::RDS::DBProxyTargetGroup AWS::RefactorSpaces::Application AWS::RefactorSpaces::Route AWS::RefactorSpaces::Service AWS::Route53Profiles::ProfileResourceAssociation AWS::S3Outposts::AccessPoint AWS::S3Outposts::Bucket AWS::SageMaker::ImageVersion AWS::ServiceCatalog::ServiceActionAssociation AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation AWS::ServiceCatalogAppRegistry::ResourceAssociation AWS::Signer::ProfilePermission AWS::SSO::Application AWS::SSO::Assignment AWS::SSO::InstanceAccessControlAttributeConfiguration AWS::SSO::PermissionSet AWS::StepFunctions::StateMachineAlias AWS::StepFunctions::StateMachineVersion AWS::Transfer::Agreement AWS::VerifiedPermissions::IdentitySource AWS::VerifiedPermissions::Policy AWS::VerifiedPermissions::PolicyTemplate AWS::VpcLattice::AccessLogSubscription AWS::VpcLattice::Listener AWS::VpcLattice::Rule AWS::WAFv2::IPSet AWS::WAFv2::RegexPatternSet AWS::WAFv2::RuleGroup AWS::WAFv2::WebACL AWS::WorkSpacesWeb::IdentityProvider ``` ### Alternatives[​](#alternatives "Direct link to Alternatives") * **Contact us**: If you require support for any of these resources, please reach out to our team for assistance. * **Submit a feature request**: Contribute to our integration's improvement by [submitting a feature request](https://roadmap.port.io/). * **Contribute directly**: Developers are encouraged to [contribute](https://github.com/port-labs/ocean/blob/main/CONTRIBUTING.md) by adding support for these resources [here](https://github.com/port-labs/ocean/tree/main/integrations/aws). ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### `useGetResourceAPI` property support[​](#usegetresourceapi-property-support "Direct link to usegetresourceapi-property-support") * By default the integration uses the [`CloudControl:ListResources`](https://docs.aws.amazon.com/cloudcontrolapi/latest/APIReference/API_ListResources.html) API to get the resources. The integration can also enrich each resource by running [`CloudControl:GetResource`](https://docs.aws.amazon.com/cloudcontrolapi/latest/APIReference/API_GetResource.html) on each resource, you can use this by enabling `useGetResourceAPI` option. The `useGetResourceAPI` option is only available for resources that support the `CloudControl:GetResource` API. ``` resources: - kind: AWS::Lambda::Function selector: query: 'true' # JQ boolean query. If evaluated to false - skip syncing the object. useGetResourceAPI: 'true' port: entity: mappings: # Mappings between one AWS object to a Port entity. Each value is a JQ query. identifier: '.Identifier' title: '.Properties.FunctionName' blueprint: 'lambda' properties: kind: '.__Kind' region: '.__Region' link: "'https://console.aws.amazon.com/go/view?arn=' + .Properties.Arn" description: '.Properties.Description' memorySize: '.Properties.MemorySize' ephemeralStorageSize: '.Properties.EphemeralStorage.Size' timeout: '.Properties.Timeout' runtime: '.Properties.Runtime' packageType: '.Properties.PackageType' environment: '.Properties.Environment' architectures: '.Properties.Architectures' layers: '.Properties.Layers' tags: '.Properties.Tags' iamRole: "'https://console.aws.amazon.com/go/view?arn=' + .Properties.Role" arn: '.Properties.Arn' relations: account: '.__AccountId' ``` **Note: Using the `useGetResourceAPI` option will make each resync run slower and use a lot more memory and cpu so you might want to add memory and cpu limits.** Get an example of the AWS resource properties To get an example of the AWS resource properties, you can use the [AWS Cloud Control API](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/what-is-cloudcontrolapi.html) to get the resource properties. For example for the `AWS::Lambda::Function` resource, you can use the following command to get the resource properties: ``` aws cloudcontrol list-resources --type-name AWS::Lambda::Function --max-items 1 | jq .ResourceDescriptions ``` ### Querying resources from specific regions[​](#querying-resources-from-specific-regions "Direct link to Querying resources from specific regions") The `regionPolicy` option allows users to define a policy for querying resources in specific AWS regions. This feature enables finer control over which AWS regions are included or excluded when fetching resources. The `regionPolicy` option works with `allow` and `deny` lists to specify allowed or restricted regions. Multi-account setups In multi-account integrations, `regionPolicy` is evaluated **per account**. A region must be enabled in each account for resources to sync. See the [multi-account region policy documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/multi_account/.md#region-policy-in-multi-account-setups) for details. * **allow**: A list of regions explicitly permitted for querying. * **deny**: A list of regions explicitly restricted from querying. #### How `regionPolicy` Works[​](#how-regionpolicy-works "Direct link to how-regionpolicy-works") 1. **If both lists are empty**: All regions are allowed. 2. **If the region is in `deny`**: It is excluded unless explicitly allowed. 3. **If the region is in `allow`**: It is included for querying. 4. **If a region appears in both lists**: It is excluded. 5. **If only `deny` is specified**: Only regions in the `deny` list are excluded. 6. **If only `allow` is specified**: Only regions in the `allow` list are included. #### Example Configuration[​](#example-configuration "Direct link to Example Configuration") ``` resources: - kind: AWS::Lambda::Function selector: query: 'true' useGetResourceAPI: 'true' regionPolicy: allow: ["us-east-1", "eu-west-1"] deny: ["us-west-2"] port: entity: mappings: identifier: '.Identifier' title: '.Properties.FunctionName' blueprint: 'lambda' properties: region: '.__Region' description: '.Properties.Description' arn: '.Properties.Arn' relations: account: '.__AccountId' ``` In this example, resources in the `us-east-1` and `eu-west-1` regions are allowed, while `us-west-2` is denied. ## Mapping the resource to Port[​](#mapping-the-resource-to-port "Direct link to Mapping the resource to Port") After you've found the resource in the [AWS Cloud Control API Docs](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html), you can map it to Port by following these steps: ### Compute resources blueprint example[​](#compute-resources-blueprint-example "Direct link to Compute resources blueprint example") Create a Port blueprint definition for the resource. The blueprint definition is based on the resource API specified per asset type. A few examples of blueprints for compute resources are provided below: **Lightsail Instance Blueprint (click to expand)** Create in Port ``` { "identifier": "lightsailInstance", "description": "This blueprint represents an AWS Lightsail Instance in our software catalog", "title": "Lightsail Instance", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "state": { "type": "string", "title": "State" }, "blueprintId": { "type": "string", "title": "Blueprint ID" }, "bundleId": { "type": "string", "title": "Bundle ID" }, "arn": { "type": "string", "title": "ARN" }, "privateIpAddress": { "type": "string", "title": "Private IP Address" }, "publicIpAddress": { "type": "string", "title": "Public IP Address" }, "cpuCount": { "type": "number", "title": "CPU Count" }, "ramSize": { "type": "number", "title": "RAM Size (GB)" }, "regionName": { "type": "string", "title": "Region Name" }, "availabilityZone": { "type": "string", "title": "Availability Zone" }, "monthlyTransfer": { "type": "number", "title": "Monthly Transfer (GB)" }, "ports": { "type": "array", "items": { "type": "string" }, "title": "Ports" }, "username": { "type": "string", "title": "Username" }, "tags": { "type": "array", "items": { "type": "string" }, "title": "Tags" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Elastic Beanstalk Application Blueprint (click to expand)** Create in Port ``` { "identifier": "elasticBeanstalkApplication", "description": "This blueprint represents an AWS Elastic Beanstalk Application in our software catalog", "title": "Elastic Beanstalk Application", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "applicationName": { "type": "string", "title": "Application Name" }, "maxAgeInDays": { "type": "number", "title": "Max Age In Days" }, "maxCount": { "type": "number", "title": "Max Count" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **ECS Service Blueprint (click to expand)** Create in Port ``` { "identifier": "ecsService", "description": "This blueprint represents an AWS ECS Service in our software catalog", "title": "ECS Service", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "desiredCount": { "type": "number", "title": "Desired Count" }, "taskDefinition": { "type": "string", "title": "Task Definition" }, "launchType": { "type": "string", "enum": ["EC2", "FARGATE", "EXTERNAL"], "title": "Launch Type" }, "schedulingStrategy": { "type": "string", "enum": ["REPLICA", "DAEMON"], "title": "Scheduling Strategy" }, "loadBalancers": { "type": "array", "title": "Load Balancers" }, "securityGroups": { "type": "array", "title": "Security Groups" }, "subnets": { "type": "array", "title": "Subnets" }, "iamRole": { "type": "string", "format": "url", "title": "IAM Role", "icon": "Unlock" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **SQS Blueprint (click to expand)** Create in Port ``` { "identifier": "sqs", "description": "This blueprint represents an AWS SQS service in our software catalog", "title": "SQS", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Lambda Blueprint (click to expand)** Create in Port ``` { "identifier": "lambda", "description": "This blueprint represents an AWS Lambda function in our software catalog", "title": "Lambda", "icon": "Lambda", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "description": { "type": "string", "title": "Description" }, "memorySize": { "type": "number", "title": "Memory Size" }, "ephemeralStorageSize": { "type": "number", "title": "Ephemeral Storage Size" }, "timeout": { "type": "number", "title": "Timeout" }, "runtime": { "type": "string", "title": "Runtime" }, "packageType": { "type": "string", "enum": ["Image", "Zip"], "title": "Package Type" }, "environment": { "type": "object", "title": "Environment" }, "architectures": { "type": "array", "items": { "type": "string", "enum": ["x86_64", "arm64"] }, "title": "Architectures" }, "layers": { "type": "array", "title": "Layers" }, "tags": { "type": "array", "title": "Tags" }, "iamRole": { "type": "string", "format": "url", "title": "IAM Role", "icon": "Unlock" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` ### Compute resources integration configuration example[​](#compute-resources-integration-configuration-example "Direct link to Compute resources integration configuration example") Create an integration configuration for the resource. The integration configuration is a YAML file that describes the ETL process to load data into the developer portal. **Mapping Configuration for Lambda, ECS Service, SQS, Lightsail, ElasticBeanstalk (click to expand)** ``` resources: - kind: AWS::Lambda::Function selector: query: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.FunctionName blueprint: '"lambda"' properties: kind: .__Kind region: .__Region link: "'https://console.aws.amazon.com/go/view?arn=' + .Properties.Arn" description: .Properties.Description memorySize: .Properties.MemorySize ephemeralStorageSize: .Properties.EphemeralStorage.Size timeout: .Properties.Timeout runtime: .Properties.Runtime packageType: .Properties.PackageType environment: .Properties.Environment architectures: .Properties.Architectures layers: .Properties.Layers tags: .Properties.Tags iamRole: "'https://console.aws.amazon.com/go/view?arn=' + .Properties.Role" arn: .Properties.Arn relations: account: .__AccountId - kind: AWS::ECS::Service selector: query: 'true' port: entity: mappings: identifier: .Identifier | split(":")[5] | split("/")[2] | split("|")[0] title: .Identifier blueprint: '"ecsService"' properties: kind: .__Kind region: .__Region link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.ServiceArn desiredCount: .Properties.DesiredCount launchType: .Properties.LaunchType cluster: .Properties.Cluster schedulingStrategy: .Properties.SchedulingStrategy loadBalancers: .Properties.LoadBalancers securityGroups: >- .Properties.NetworkConfiguration.AwsvpcConfiguration.SecurityGroups subnets: .Properties.NetworkConfiguration.AwsvpcConfiguration.Subnets taskDefinition: .Properties.TaskDefinition iamRole: >- .Role | if . == null then null else 'https://console.aws.amazon.com/go/view?arn=' + . end arn: .Properties.ServiceArn relations: account: .__AccountId - kind: AWS::SQS::Queue selector: query: 'true' port: entity: mappings: identifier: .Identifier | split("/")[4] title: .Properties.QueueUrl | split("/")[4] blueprint: '"sqs"' properties: kind: .__Kind region: .__Region link: .Properties.QueueUrl relations: account: .__AccountId - kind: AWS::Lightsail::Instance selector: query: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.InstanceName blueprint: '"lightsailInstance"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.InstanceArn state: .Properties.State.Name blueprintId: .Properties.BlueprintId bundleId: .Properties.BundleId arn: .Properties.InstanceArn privateIpAddress: .Properties.PrivateIpAddress publicIpAddress: .Properties.PublicIpAddress cpuCount: .Properties.Hardware.CpuCount ramSize: .Properties.Hardware.RamSizeInGb regionName: Properties.RegionName availabilityZone: .Properties.AvailabilityZone monthlyTransfer: .Properties.MonthlyTransfer.GbPerMonthAllocated ports: .Properties.Networking.Ports[](.FromPort | tostring + ' - ' + .ToPort | tostring + ' - ' + .AccessDirection) username: .Properties.UserName tags: .Properties.Tags relations: account: .__AccountId - kind: AWS::ElasticBeanstalk::Application selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.ApplicationName blueprint: '"elasticBeanstalkApplication"' properties: link: >- 'https://console.aws.amazon.com/elasticbeanstalk/home?region=' + .__Region + '/application/overview?applicationName=' + .Properties.ApplicationName applicationName: .Properties.ApplicationName maxAgeInDays: .Properties.ResourceLifecycleConfig.VersionLifecycleConfig.MaxAgeRule.MaxAgeInDays maxCount: .Properties.ResourceLifecycleConfig.VersionLifecycleConfig.MaxCountRule.MaxCount kind: .__Kind region: .__Region relations: account: .__AccountId ``` ### Developer tools blueprint and configuration example[​](#developer-tools-blueprint-and-configuration-example "Direct link to Developer tools blueprint and configuration example") **Amplify App Blueprint (click to expand)** Create in Port ``` { "identifier": "amplifyApp", "description": "This blueprint represents an AWS Amplify App in our software catalog", "title": "Amplify App", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "appId": { "type": "string", "title": "App ID" }, "repository": { "type": "string", "title": "Repository" }, "platform": { "type": "string", "title": "Platform" }, "defaultDomain": { "type": "string", "title": "Default Domain" }, "customHeaders": { "type": "string", "title": "Custom Headers" }, "appName": { "type": "string", "title": "App Name" }, "arn": { "type": "string", "title": "ARN" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Mapping Configuration for Amplify App (click to expand)** ``` resources: - kind: AWS::Amplify::App selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.AppName blueprint: '"amplifyApp"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.Arn appId: .Properties.AppId repository: .Properties.Repository platform: .Properties.Platform defaultDomain: .Properties.DefaultDomain customHeaders: .Properties.CustomHeaders appName: .Properties.AppName arn: .Properties.Arn kind: .__Kind region: .__Region relations: account: .__AccountId ``` ### Application integration blueprint and configuration example[​](#application-integration-blueprint-and-configuration-example "Direct link to Application integration blueprint and configuration example") **Kinesis Stream Blueprint (click to expand)** Create in Port ``` { "identifier": "kinesisStream", "description": "This blueprint represents an AWS Kinesis Stream in our software catalog", "title": "Kinesis Stream", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "streamMode": { "type": "string", "title": "Stream Mode" }, "shardCount": { "type": "number", "title": "Shard Count" }, "retentionPeriodHours": { "type": "number", "title": "Retention Period Hours" }, "shardLevelMetrics": { "type": "array", "items": { "type": "string" }, "title": "Shard Level Metrics" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **SNS Topic Blueprint (click to expand)** Create in Port ``` { "identifier": "snsTopic", "description": "This blueprint represents an AWS SNS Topic in our software catalog", "title": "SNS Topic", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "arn": { "type": "string", "title": "ARN" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Mapping Configuration for Kinesis Stream and SNS Topic (click to expand)** ``` resources: - kind: AWS::Kinesis::Stream selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.Name blueprint: '"kinesisStream"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.Arn streamMode: .Properties.StreamModeDetails.StreamMode shardCount: .Properties.ShardCount retentionPeriodHours: .Properties.RetentionPeriodHours shardLevelMetrics: .Properties.DesiredShardLevelMetrics arn: .Properties.Arn relations: account: .__AccountId - kind: AWS::SNS::Topic selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.TopicName blueprint: '"snsTopic"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.TopicArn arn: .Properties.TopicArn kind: .__Kind region: .__Region relations: account: .__AccountId ``` ### Machine learning blueprint and configuration example[​](#machine-learning-blueprint-and-configuration-example "Direct link to Machine learning blueprint and configuration example") **Lex Bot Blueprint (click to expand)** Create in Port ``` { "identifier": "lexBot", "description": "This blueprint represents an AWS Lex Bot in our software catalog", "title": "Lex Bot", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "arn": { "type": "string", "title": "ARN" }, "role": { "type": "string", "title": "Role ARN" }, "name": { "type": "string", "title": "Name" }, "idleSessionTTLInSeconds": { "type": "number", "title": "Idle Session TTL In Seconds" }, "dataPrivacy": { "type": "object", "title": "Data Privacy", "properties": { "childDirected": { "type": "boolean", "title": "Child Directed" } } }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Mapping Configuration for Lex Bot (click to expand)** ``` resources: - kind: AWS::Lex::Bot selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.Name blueprint: '"lexBot"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.Arn arn: .Properties.Arn role: .Properties.RoleArn name: .Properties.Name idleSessionTTLInSeconds: .Properties.IdleSessionTTLInSeconds dataPrivacy: .Properties.DataPrivacy kind: .__Kind region: .__Region relations: account: .__AccountId ``` ### Management and governance blueprint and configuration example[​](#management-and-governance-blueprint-and-configuration-example "Direct link to Management and governance blueprint and configuration example") **Auto Scaling Group Blueprint (click to expand)** Create in Port ``` { "identifier": "autoScalingGroup", "description": "This blueprint represents an AWS Auto Scaling Group in our software catalog", "title": "Auto Scaling Group", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "desiredCapacity": { "type": "number", "title": "Desired Capacity" }, "minSize": { "type": "number", "title": "Minimum Size" }, "maxSize": { "type": "number", "title": "Maximum Size" }, "arn": { "type": "string", "title": "ARN" }, "role": { "type": "string", "format": "url", "title": "Service Linked Role ARN" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **CloudFormation Stack Blueprint (click to expand)** Create in Port ``` { "identifier": "cloudformationStack", "description": "This blueprint represents an AWS CloudFormation Stack in our software catalog", "title": "CloudFormation Stack", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "arn": { "type": "string", "title": "ARN" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Mapping Configuration for CloudFormation Stack and Auto Scaling Group (click to expand)** ``` resources: - kind: AWS::CloudFormation::Stack selector: query: 'true' port: entity: mappings: identifier: .StackId title: .StackName blueprint: '"cloudformationStack"' properties: link: '"https://console.aws.amazon.com/go/view?arn=" + .StackId' arn: .StackId kind: .__Kind region: .__Region relations: account: .__AccountId - kind: AWS::AutoScaling::AutoScalingGroup selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.AutoScalingGroupName blueprint: '"autoScalingGroup"' properties: link: >- 'https://console.aws.amazon.com/ec2/home?region=' + .__Region + '#AutoScalingGroupDetails:id=' + .Properties.AutoScalingGroupName + ';view=details' desiredCapacity: .Properties.DesiredCapacity minSize: .Properties.MinSize maxSize: .Properties.MaxSize arn: .Properties.AutoScalingGroupARN role: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.ServiceLinkedRoleARN kind: .__Kind region: .__Region relations: account: .__AccountId ``` ### Networking and content delivery blueprint and configuration example[​](#networking-and-content-delivery-blueprint-and-configuration-example "Direct link to Networking and content delivery blueprint and configuration example") **CloudFront Distribution Blueprint (click to expand)** Create in Port ``` { "identifier": "cloudFrontDistribution", "description": "This blueprint represents an AWS CloudFront Distribution in our software catalog", "title": "CloudFront Distribution", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "status": { "type": "string", "title": "Status" }, "domainName": { "type": "string", "title": "Domain Name" }, "lastModifiedTime": { "type": "string", "format": "date-time", "title": "Last Modified Time" }, "allowedMethods": { "type": "array", "items": { "type": "string" }, "title": "Allowed Methods" }, "originDomainNames": { "type": "array", "items": { "type": "string" }, "title": "Origin Domain Names" }, "originIds": { "type": "array", "items": { "type": "string" }, "title": "Origin IDs" }, "enabled": { "type": "boolean", "title": "Enabled" }, "isIpv6Enabled": { "type": "boolean", "title": "IPv6 Enabled" }, "httpVersion": { "type": "string", "title": "HTTP Version" }, "isStaging": { "type": "boolean", "title": "Is Staging" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Mapping Configuration for CloudFront Distribution (click to expand)** ``` resources: - kind: AWS::CloudFront::Distribution selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.DomainName blueprint: '"cloudFrontDistribution"' properties: link: >- 'https://console.aws.amazon.com/cloudfront/home?region=' + .__Region + '#/distribution/' + .Properties.Id status: .Properties.Status domainName: .Properties.DomainName lastModifiedTime: .Properties.LastModifiedTime allowedMethods: .Properties.DefaultCacheBehavior.AllowedMethods originDomainNames: .Properties.Origins[].DomainName originIds: .Properties.Origins[].Id enabled: .Properties.Enabled isIpv6Enabled: .Properties.IPV6Enabled httpVersion: .Properties.HttpVersion isStaging: .Properties.Staging kind: .__Kind region: .__Region relations: account: .__AccountId ``` ### Security, identity, and compliance blueprint and configuration example[​](#security-identity-and-compliance-blueprint-and-configuration-example "Direct link to Security, identity, and compliance blueprint and configuration example") **Cognito User Pool Blueprint (click to expand)** Create in Port ``` { "identifier": "cognitoUserPool", "description": "This blueprint represents an AWS Cognito User Pool in our software catalog", "title": "Cognito User Pool", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "arn": { "type": "string", "title": "ARN" }, "userPoolId": { "type": "string", "title": "User Pool ID" }, "providerName": { "type": "string", "title": "Provider Name" }, "status": { "type": "string", "title": "Status" }, "creationDate": { "type": "string", "format": "date-time", "title": "Creation Date" }, "mfaConfiguration": { "type": "string", "title": "MFA Configuration" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Mapping Configuration for Cognito User Pool (click to expand)** ``` resources: - kind: AWS::Cognito::UserPool selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.UserPoolName blueprint: '"cognitoUserPool"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.Arn arn: .Properties.Arn userPoolId: .Properties.UserPoolId providerName: .Properties.ProviderName status: .Properties.Status creationDate: .Properties.CreationDate mfaConfiguration: .Properties.MfaConfiguration kind: .__Kind region: .__Region relations: account: .__AccountId ``` ### Storage blueprint and configuration example[​](#storage-blueprint-and-configuration-example "Direct link to Storage blueprint and configuration example") **DynamoDB Table Blueprint (click to expand)** Create in Port ``` { "identifier": "dynamoDBTable", "description": "This blueprint represents an AWS DynamoDB Table in our software catalog", "title": "DynamoDB Table", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "tableStatus": { "type": "string", "title": "Table Status" }, "itemCount": { "type": "number", "title": "Item Count" }, "creationDateTime": { "type": "string", "format": "date-time", "title": "Creation Date Time" }, "billingMode": { "type": "string", "title": "Billing Mode" }, "writeCapacityUnits": { "type": "number", "title": "Write Capacity Units" }, "readCapacityUnits": { "type": "number", "title": "Read Capacity Units" }, "timeToLive": { "type": "boolean", "title": "Time to Live" }, "pointInTimeRecovery": { "type": "boolean", "title": "Point in Time Recovery" }, "deletionProtection": { "type": "boolean", "title": "Deletion Protection" }, "arn": { "type": "string", "title": "ARN" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **ElastiCache Serverless Cache Blueprint (click to expand)** Create in Port ``` { "identifier": "elasticacheServerless", "description": "This blueprint represents an AWS ElastiCache Serverless Cache in our software catalog", "title": "ElastiCache Serverless Cache", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "engine": { "type": "string", "title": "Engine" }, "engineVersion": { "type": "string", "title": "Full Engine Version" }, "status": { "type": "string", "title": "Status" }, "description": { "type": "string", "title": "Description" }, "createTime": { "type": "string", "format": "date-time", "title": "Create Time" }, "securityGroupIds": { "type": "array", "items": { "type": "string" }, "title": "Security Group IDs" }, "subnetIds": { "type": "array", "items": { "type": "string" }, "title": "Subnet IDs" }, "endpoint": { "type": "string", "title": "Endpoint" }, "port": { "type": "number", "title": "Port" }, "readerEndpoint": { "type": "string", "title": "Reader Endpoint" }, "readerPort": { "type": "number", "title": "Reader Port" }, "dailySnapshotTime": { "type": "string", "title": "Daily Snapshot Time" }, "snapshotRetentionLimit": { "type": "number", "title": "Snapshot Retention Limit" }, "serverlessCacheName": { "type": "string", "title": "Serverless Cache Name" }, "arn": { "type": "string", "title": "ARN" }, "kind": { "type": "string", "title": "Kind" }, "region": { "type": "string", "title": "Region" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **RDS Instance Blueprint (click to expand)** Create in Port ``` { "identifier": "rdsInstance", "description": "This blueprint represents an AWS RDS DBInstance in our software catalog", "title": "RDS Instance", "icon": "AWS", "schema": { "properties": { "link": { "type": "string", "format": "url", "title": "Link" }, "dbInstanceClass": { "type": "string", "title": "DB Instance Class" }, "dbInstanceStatus": { "type": "string", "title": "DB Instance Status" }, "engine": { "type": "string", "title": "Engine" }, "storageType": { "type": "string", "title": "Storage Type" }, "engineVersion": { "type": "string", "title": "Engine Version" }, "port": { "type": "number", "title": "Port" }, "allocatedStorage": { "type": "number", "title": "Allocated Storage" }, "endpoint": { "type": "string", "title": "Endpoint" }, "multiAZ": { "type": "boolean", "title": "Multi-AZ" }, "deletionProtection": { "type": "boolean", "title": "Deletion Protection" }, "availabilityZone": { "type": "string", "title": "Availability Zone" }, "masterUsername": { "type": "string", "title": "Master Username" }, "publicAccess": { "type": "boolean", "title": "Public Access" }, "vpcSecurityGroups": { "type": "array", "items": { "type": "string" }, "title": "VPC Security Groups" }, "arn": { "type": "string", "title": "ARN" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "account": { "title": "Account", "target": "awsAccount", "required": true, "many": false } } } ``` **Mapping Configuration for RDS Instance, ElastiCache, DynamoDB Table (click to expand)** ``` resources: - kind: AWS::RDS::DBInstance selector: query: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.DBInstanceIdentifier blueprint: '"rdsInstance"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.DBInstanceArn dbInstanceClass: .Properties.DBInstanceClass dbInstanceStatus: .Properties.DBInstanceStatus engine: .Properties.Engine storageType: .Properties.StorageType engineVersion: .Properties.EngineVersion port: .Properties.Endpoint.Port allocatedStorage: .Properties.AllocatedStorage endpoint: .Properties.Endpoint.Address multiAZ: .Properties.MultiAZ deletionProtection: .Properties.DeletionProtection availabilityZone: .Properties.AvailabilityZone masterUsername: .Properties.MasterUsername publicAccess: .Properties.PubliclyAccessible vpcSecurityGroups: .Properties.VpcSecurityGroups arn: .Properties.DBInstanceArn relations: account: .__AccountId - kind: AWS::DynamoDB::Table selector: query: 'true' useGetResourceAPI: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.TableName blueprint: '"dynamoDBTable"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.Arn tableStatus: .Properties.TableStatus itemCount: .Properties.ItemCount creationDateTime: .Properties.CreationDateTime billingMode: .Properties.BillingMode writeCapacityUnits: .Properties.ProvisionedThroughput.WriteCapacityUnits readCapacityUnits: .Properties.ProvisionedThroughput.ReadCapacityUnits timeToLive: .Properties.TimeToLiveSpecification.Enabled pointInTimeRecovery: .Properties.PointInTimeRecoverySpecification.PointInTimeRecoveryEnabled deletionProtection: .Properties.DeletionProtectionEnabled arn: .Properties.Arn kind: .__Kind region: .__Region relations: account: .__AccountId - kind: AWS::ElastiCache::ServerlessCache selector: query: 'true' port: entity: mappings: identifier: .Identifier title: .Properties.ServerlessCacheName blueprint: '"elasticacheServerless"' properties: link: >- 'https://console.aws.amazon.com/go/view?arn=' + .Properties.ARN engine: .Properties.Engine engineVersion: .Properties.FullEngineVersion status: .Properties.Status description: .Properties.Description createTime: .Properties.CreateTime securityGroupIds: .Properties.SecurityGroupIds subnetIds: .Properties.SubnetIds endpoint: .Properties.Endpoint.Address port: .Properties.Endpoint.Port readerEndpoint: .Properties.ReaderEndpoint.Address readerPort: .Properties.ReaderEndpoint.Port dailySnapshotTime: .Properties.DailySnapshotTime snapshotRetentionLimit: .Properties.SnapshotRetentionLimit serverlessCacheName: .Properties.ServerlessCacheName arn: .Properties.ARN kind: .__Kind region: .__Region relations: account: .__AccountId ``` --- # Live events setup Port's AWS integration supports real-time event processing, allowing for accurate representation of your AWS infrastructure inside Port. This guide explains how to set up live events for your AWS resources. Supported account type Live events are currently only available for **Single account installations** (not multi-account). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before setting up live events, ensure you have: * Complete the [AWS integration installation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md). * Your Port API key for authentication. * Permissions to create EventBridge rules on your AWS account. Terraform vs Manual Installation * **Terraform users**: Use the provided Terraform module for automated setup. * **Manual installation users**: Follow the AWS console setup steps. ## Live-events flow[​](#live-events-flow "Direct link to Live-events flow") The live events flow is comprised of the following steps: 1. **AWS Services** generate events when resources change. 2. **CloudTrail** captures these events. 3. **EventBridge Rules** filter and route specific events. 4. **API Gateway** receives the events and forwards them to Port. 5. Your **Port Integration** processes the events and updates your software catalog. ![AWS live events flow from CloudTrail through EventBridge rules and API Gateway to the load balancer and ECS-hosted Port integration](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/live-events-diagram.svg)
## Setup methods[​](#setup-methods "Direct link to Setup methods") * Terraform (Recommended) * Manual AWS Console Setup If you installed the AWS integration using Terraform, use the provided module to set up live events. ### Supported resource types The default Terraform module supports live events for these resource types: * **EC2 Instances** (`AWS::EC2::Instance`) * **S3 Buckets** (`AWS::S3::Bucket`) * **CloudFormation Stacks** (`AWS::CloudFormation::Stack`) ### Add custom resource types To add live events for additional resource types (like SSM Parameters), use the `aws_event_rule` module: **AWS event rule module (click to expand)** ``` module "aws_event_rule" { source = "port-labs/integration-factory/ocean//modules/aws_helpers/event" name = "port-aws-ocean-sync-ssm-parameters" description = "Capture Parameter Store change events" event_pattern = { source = ["aws.ssm"] detail-type = ["Parameter Store Change"] } input_paths = { resource_type = "AWS::SSM::Parameter" account_id = "$.account" aws_region = "$.region" event_name = "$.detail-type" identifier = "$.resources.0" } api_key_param = "" target_arn = "/production/POST/integration/webhook" } ``` **Configuration parameters (click to expand)** | Parameter | Description | Example | | --------------- | ------------------------------------------- | -------------------------------------------------------------------- | | `name` | EventBridge rule name | `"port-aws-ocean-sync-ssm-parameters"` | | `description` | Rule description | `"Capture Parameter Store change events"` | | `event_pattern` | AWS event pattern to match | `{ source = ["aws.ssm"], detail-type = ["Parameter Store Change"] }` | | `input_paths` | JSON path mappings for event transformation | See example above | | `api_key_param` | Port API key parameter | `""` | | `target_arn` | API Gateway target ARN | `"/production/POST/integration/webhook"` | If you installed the AWS integration manually, follow these steps to create EventBridge rules in the AWS console: ### Step 1: Create a rule 1. Go to **EventBridge** → **Rules** → **Create rule**. 2. **Rule name**: Give it a descriptive name (e.g., `port-live-updates-ssm`). 3. Click **Next**. ### Step 2: Define the event pattern 1. **Event source**: Select "AWS events or services". 2. **Event service**: Select the relevant AWS service (e.g., "Systems Manager"). 3. **Event type**: Select the type of event (e.g., "Parameter Store"). 4. **Event Type Specification**: Select "Specific detail type(s)" and choose the event type (e.g., "Parameter Store Change"). 5. Click **Next**. ### Step 3: Configure the target 1. **Target type**: Select "AWS Service". 2. **Target**: Select "API Gateway". 3. **Target location**: Select "Target in this account". 4. **API**: Select the API Gateway created for your integration. 5. **Deployment stage**: Select "production". 6. **Integration target**: Enter `/integration/webhook` (HTTP POST). ### Step 4: Add required headers Add these required headers: | Header Name | Value | | -------------------------- | ------------------------------------------ | | `Content-Type` | `application/json` | | `x-port-aws-ocean-api-key` | `` (replace with actual key) | ### Step 5: Transform the Event Data Port expects a simplified payload. Use Input Transformer to map the raw AWS event: **Input Path (mapping):** ``` { "accountId": "$.account", "awsRegion": "$.region", "eventName": "$.detail-type", "identifier": "$.resources.0" } ``` **Template (output):** ``` { "resource_type": "AWS::SSM::Parameter", "accountId": "", "awsRegion": "", "eventName": "", "identifier": "" } ``` Resource Type Mapping Replace `"AWS::SSM::Parameter"` with the appropriate AWS resource type: * EC2 Instances: `"AWS::EC2::Instance"` * S3 Buckets: `"AWS::S3::Bucket"` * CloudFormation Stacks: `"AWS::CloudFormation::Stack"` ### Step 6: Review & Create 1. Click **Next** → **Next** → **Create rule**. 2. AWS will now forward matching events to Port automatically. ## Supported AWS services[​](#supported-aws-services "Direct link to Supported AWS services") The complete list of AWS services that support live events can be found [here](https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/supported-resources.html). ### Add other services[​](#add-other-services "Direct link to Add other services") To add live events for additional AWS services, follow these steps: 1. Identify the service's event source and detail type. 2. Create an EventBridge rule with the appropriate pattern. 3. Configure the input transformer with the correct resource type. Default Terraform installation The default setup comes preconfigured with support for three AWS resource types: * `EC2 Instances`. * `S3 Buckets`. * `CloudFormation Stacks`. --- # Multi account support This guide will provide you with the necessary tools for enabling our Ocean AWS Legacy integration to digest multiple account's data. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before proceeding with the multi-account installation, make sure you are using **AWS Organizations** to manage your AWS accounts. ## Our Permissions model[​](#our-permissions-model "Direct link to Our Permissions model") A few key concepts in the AWS's permissions model: 1. *Policy*: A document that defines permissions, specifying what actions are allowed or denied for particular resources and under what conditions. 2. *Role*: An identity with specific permissions and trust policies. 3. *AssumeRole*: An action that allows for a user / service account to impersonate a Role with temporary credentials. Allowing roles to assume-roles for other roles is enabled via `Trust Policies`. 4. *Trust Policy*: A document attached to a role that specifies which principals (users, groups, or roles) are allowed to assume the role. 5. *Account*: A container that holds all your AWS resources and services, identified uniquely by an AWS account ID. 6. *Root Account*: The primary account created when setting up an AWS environment, the only account that has access to other accounts in the AWS Organization. 7. *Integration Account*: The account where the Port AWS integration will be installed 8. *Member Account*(s): The target accounts which the Port AWS integration will fetch resources from ### How are permissions granted?[​](#how-are-permissions-granted "Direct link to How are permissions granted?") *Very* Briefly, a permission to perform a certain action in AWS must be granted from *both* the entity which performs the action *and* the entity which is affected from the action. This means, that if I want to add permissions to read from a S3 bucket and I'm using a certain role, I *have* to give permissions to the role from the S3's permissions policy *and* to read the S3 bucket from the Role. This is an oversimplification, but it works when trying to understand how you set permissions up from scratch. For more information, check [AWS's docs](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html) Let's assume you have 2 different accounts: 1. Our `integration account`, where you deploy the Port AWS integration. 2. A `Root account`. ![AWS organization with separate root and Port integration accounts](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-single-step-1.jpg) And the final flow that allows data to be read is where the `integration account` assumes the `Root account` (to list information about the organization), and in addition, actually fetches the data from the `integration account`. ![Port integration account assuming a read-only role to describe the AWS root account while reading its own resources](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-single-step-2.jpg) First, we will create a role with necessary permissions (if you're running the terraform installation, this will be done for you). We'll call this role `ReadOnlyPermissionsOceanRole`, with `I can read resources in this account` policy. ![ReadOnlyPermissionsOceanRole granting the Port integration permission to read resources in its AWS account](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-role-step-1.jpg) Then we'll go to the root account and assign permissions to view metadata about other accounts. We'll call this role `OrganizationalOceanRole`, and grant it `permissions to list accounts within the organization`. ![OrganizationalOceanRole granting account metadata access in the AWS root account alongside the integration read-only role](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-role-step-2.jpg) Then, In order for the integration's role to get accounts metadata, We'll need to give our integration role, `ReadOnlyPermissionsOceanRole`, permissions to assume the role of the root account, `OrganizationalOceanRole`. ![AWS root account trust policy allowing the Port integration read-only role to assume OrganizationalOceanRole](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-role-step-3.jpg) Connecting it all we get the final flow: ![Complete single-account AWS permission flow for Port to read integration resources and describe the root account](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-single-step-3.jpg) That's the flow you need to understand if you want to install the integration in one account. ### How does multiple accounts work?[​](#how-does-multiple-accounts-work "Direct link to How does multiple accounts work?") If we look back at the flow from the single account, we see that it's basically the same, just with several member accounts that the integration role has the possibility to `assumeRole` into: ![AWS organization containing a root account, Port integration account, and multiple member accounts](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-multi-step-1.jpg) And the final flow is similar: ![Port integration account assuming read-only roles to list the root account and read resources from multiple AWS member accounts](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-multi-step-2.jpg) Together with the permissions and trust policies: ![Complete multi-account AWS permission model with root and member account trust policies for the Port integration role](/img/build-your-software-catalog/sync-data-to-catalog/cloud-providers/aws/aws-role-step-4.jpg) ## Multiple Accounts Setup[​](#multiple-accounts-setup "Direct link to Multiple Accounts Setup") ### Integration Account[​](#integration-account "Direct link to Integration Account") The name of this role (not the ARN) is referenced as `integration_account` in this doc.
In this example, we refer to the S3 bucket permissions. If you need to read from the `integration account`, you'll need to add CloudControl (cloudformation) and the resources permissions. **Permissions (click to expand)** ``` AWS::ReadOnlyAccess ``` ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": [ "arn:aws:iam:::role/", "arn:aws:iam:::role/" ] }, { "Effect": "Allow", "Action": "account:ListRegions", "Resource": "*" } ] } ``` ### Member account[​](#member-account "Direct link to Member account") The name of this role (not the ARN) is referenced as `accountReadRoleName` in this doc. **Permissions (click to expand)** ``` AWS::ReadOnlyAccess ``` **Trust relationships (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam:::role/" } ] } ``` ### Root Account[​](#root-account "Direct link to Root Account") The name of this role (not the ARN) is referenced as `organizationRoleArn` in this doc. **Permissions (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "organizations:Describe*", "organizations:List*" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "account:GetAlternateContact", "account:GetContactInformation", "account:ListRegions", "account:GetRegionOptStatus", "account:GetPrimaryEmail" ], "Resource": "*" } ] } ``` **Trust relationships (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::role/" }, "Action": "sts:AssumeRole", } ] } ``` ## Minimum Permissions[​](#minimum-permissions "Direct link to Minimum Permissions") This section is designed for users who wish to manage permissions manually to maintain tighter security. It is not intended for users opting for the default integration setup. To implement the minimum permissions needed for the integration while maintaining security best practices, use this configuration. It ensures that the integration functions effectively without granting excessive access, adhering to the principle of least privilege for enhanced security and control. Customize Permissions based on imported resource The permissions outlined for S3 in this section are provided as an example. It is important to note that when using the CloudControl API, additional underlying permissions for each resource type are necessary to ensure successful integration. This guide uses S3 bucket permissions as a sample, but users should customize their permissions based on the specific resources they plan to import. ### Integration Account[​](#integration-account-1 "Direct link to Integration Account") **Permissions (click to expand)** ``` { "Statement": [ { "Sid": "AssumeRolePermissions", "Action": "sts:AssumeRole", "Effect": "Allow", "Resource": [ "arn:aws:iam:::role/", "arn:aws:iam:::role/" ] }, { "Sid": "AccountPermissions", "Action": "account:ListRegions", "Effect": "Allow", "Resource": "*" }, { "Sid": "STSPermissions", "Effect": "Allow", "Action": [ "sts:GetCallerIdentity" ], "Resource": "*" }, { "Sid": "S3Permissions", "Effect": "Allow", "Action": [ "s3:Describe*", "s3:List*", "s3:Get*" ], "Resource": "*" }, { "Sid": "CloudControlAPIPermissions", "Effect": "Allow", "Action": [ "cloudformation:GetResource", "cloudformation:ListResources" ], "Resource": "*" } ], "Version": "2012-10-17" } ``` ### Member account[​](#member-account-1 "Direct link to Member account") **Permissions (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "S3Permissions", "Effect": "Allow", "Action": [ "s3:Describe*", "s3:List*", "s3:Get*" ], "Resource": "*" }, { "Sid": "AccountPermissions", "Effect": "Allow", "Action": [ "account:ListRegions" ], "Resource": "*" }, { "Sid": "CloudControlAPIPermissions", "Effect": "Allow", "Action": [ "cloudformation:GetResource", "cloudformation:ListResources" ], "Resource": "*" } ] } ``` **Trust relationships (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::role/" }, "Action": "sts:AssumeRole", } ] } ``` ### Root Account[​](#root-account-1 "Direct link to Root Account") **Permissions (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "ListAccountPermissions", "Effect": "Allow", "Action": [ "organizations:ListAccounts" ], "Resource": [ "*" ] } ] } ``` **Trust relationships (click to expand)** ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::role/" }, "Action": "sts:AssumeRole", } ] } ``` ### Expanding to multiple accounts[​](#expanding-to-multiple-accounts "Direct link to Expanding to multiple accounts") In order to keep adding accounts to the integration's scope, permissions must be delivered for and from each of the accounts. For each account you want to have, you should make sure the following applies: In each non-root account (target member account), the role `accountReadRoleName` must exist (with the same name and permissions), with `accountReadRoleName` from the integration account in it's trust policy. See [reference](#member-account) ### Region policy in multi-account setups[​](#region-policy-in-multi-account-setups "Direct link to Region policy in multi-account setups") When using [`regionPolicy`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/examples/mapping_extra_resources/.md#querying-resources-from-specific-regions) in a multi-account integration, the policy is **evaluated independently for each AWS account**. This means: * The integration checks which regions are enabled in each account (via the `account:ListRegions` permission). * A region in your `allow` list will only produce results for accounts where that region is actually enabled. * If a region is not enabled in a specific account, the integration will report "across 0 allowed regions" for that account even if the region is in the `allow` list. Common pitfall Defining `regionPolicy: { allow: ["us-east-1"] }` does **not** guarantee syncing from all accounts. Each member account must independently have `us-east-1` enabled and the `account:ListRegions` permission granted. Verify that your target regions are [enabled in each account](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-regions.html). #### Example: same region across multiple accounts[​](#example-same-region-across-multiple-accounts "Direct link to Example: same region across multiple accounts") The following mapping syncs Lambda functions from `us-east-1` and `eu-west-1` only. The integration will apply this policy to each account it assumes into, if Account A has `eu-west-1` disabled, only `us-east-1` resources will be fetched from Account A. ``` resources: - kind: AWS::Lambda::Function selector: query: 'true' regionPolicy: allow: ["us-east-1", "eu-west-1"] port: entity: mappings: identifier: '.Identifier' title: '.Properties.FunctionName' blueprint: '"lambda"' properties: region: '.__Region' account: '.__AccountId' ``` ### Running the integration[​](#running-the-integration "Direct link to Running the integration") Now, after you set-up permissions properly, You can run your integration with two new integration configurations: 1. `organizationRoleArn` - Which represents the root-account's role-assuming delegation role 2. `accountReadRoleName` - Which represents the name of the roles spread in all the accounts we want to assume-role to These should be provided when [installing the integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) if you want to enable multiple account support. --- # Azure ![Azure icon](/img/guides/icons/Azure.svg)![Azure icon](/img/guides/icons/Azure.svg) Loading version... Our integration with Azure provides the ability to export your Azure resources to Port, according to your configuration. After the initial import of data, the integration will also listen to live events from Azure to update data inside Port in real time. Supported resources ![External link icon](/img/icons/external-link.svg)[Cloud Resource](https://learn.microsoft.com/en-us/rest/api/resources/resources/list)[Subscription](https://learn.microsoft.com/en-us/rest/api/resources/subscriptions)[Resource Group](https://learn.microsoft.com/en-us/rest/api/resources/resource-groups)[Container App](https://learn.microsoft.com/en-us/rest/api/containerapps/)[Storage Account](https://learn.microsoft.com/en-us/rest/api/storagerp/storage-accounts)[Virtual Machine](https://learn.microsoft.com/en-us/rest/api/compute/virtual-machines)[Managed Cluster](https://learn.microsoft.com/en-us/rest/api/aks/managed-clusters)[Load Balancer](https://learn.microsoft.com/en-us/rest/api/load-balancer/) ## Core support [Live eventsSee supported resources](#sync-approaches) Additional supported resources The resource list above contains examples, not the complete list of supported resources. Any Azure resource type with a subscription-level `List` API works here. See [mapping extra resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/mapping_extra_resources/.md) to check support and configure additional resource types. Port's Azure exporter is open source. View the [Azure exporter source code](https://github.com/port-labs/ocean/tree/main/integrations/azure). ## Common use cases[​](#common-use-cases "Direct link to Common use cases") Our Azure integration makes it easy to fill the software catalog with data directly from your Azure subscription, for example: * Map resources from your Azure subscriptions, such as **AKS**, **Storage Accounts**, **Container Apps**, **Load Balancers** and other Azure resources. * Watch for Azure object changes (create/update/delete) in real-time, and automatically apply the changes to your entities in Port. * Configure relations to other resources in your organization to create complete, easily digestible views of your resources and their relationships inside Port. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Helm2Prerequisites3Install Helm * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI/CD * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Terraform * ✧Deploy using Infrastructure as Code * ✧Real-time updates via event-based triggers * ✧Reproducible and version-controlled deployments ArgoCD * ✧Deploy on Kubernetes via ArgoCD * ✧GitOps workflow for managing the integration * ✧Ideal for teams already using ArgoCD On-Prem (one-time) * ✧Run as a Docker container on any platform * ✧One-time or on-demand execution * ✧Minimal setup requirements Prerequisites ## Sync approaches[​](#sync-approaches "Direct link to Sync approaches") Port offers two approaches for synchronizing Azure resources: | | Azure REST exporter | Azure resource graph exporter | | --------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | | **Sync method** | Full resource scan via Azure Resource Manager (ARM) REST API | Fast sync via [Azure Resource Graph](https://learn.microsoft.com/en-us/azure/governance/resource-graph/reference/supported-tables-resources) | | **Real-time updates** | Yes — via Azure Event Grid (Terraform deployment only) | No | | **Deployment** | Helm, Docker, or ContainerApp | Helm, Docker, or ContainerApp | | **Best for** | Comprehensive resource visibility with full schemas and real-time Event Grid notifications | Speed and efficiency at massive scale (thousands of subscriptions) | Choosing the right approach Choose the **Azure REST exporter** when you need the full resource schema or real-time Event Grid notifications. Choose the **Azure resource graph exporter** when syncing at scale is the priority and you don't need Event Grid infrastructure. ## Ingest Azure resources[​](#ingest-azure-resources "Direct link to Ingest Azure resources") The Azure exporter can retrieve all the resources supported by the [Azure Resource Manager REST API](https://learn.microsoft.com/en-us/rest/api/resources/resources/list), and export them to Port as entities of existing blueprints. For examples on how to map resources head to the [resource templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/.md) page. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration you get after installing the Azure integration. **Default mapping configuration (click to expand)** ``` resources: - kind: subscription selector: query: 'true' apiVersion: '2022-09-01' port: entity: mappings: identifier: .id title: .display_name blueprint: '"azureSubscription"' properties: tags: .tags - kind: Microsoft.Resources/resourceGroups selector: query: 'true' apiVersion: '2022-09-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureResourceGroup"' properties: location: .location provisioningState: .properties.provisioningState + .properties.provisioning_state tags: .tags relations: subscription: .id | split("/") | .[1] |= ascii_downcase |.[2] |= ascii_downcase | .[:3] |join("/") - kind: Microsoft.App/containerApps selector: query: 'true' apiVersion: '2022-03-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.Storage/storageAccounts selector: query: 'true' apiVersion: '2023-01-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.Compute/virtualMachines selector: query: 'true' apiVersion: '2023-03-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.ContainerService/managedClusters selector: query: 'true' apiVersion: '2023-05-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.Network/loadBalancers selector: query: 'true' apiVersion: '2023-02-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view the mapping and blueprint definition for each resource type. For additional resources and advanced configurations, see the [resource templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/.md) page. SubscriptionResource GroupContainer AppStorage AccountVirtual MachineManaged ClusterLoad Balancer BlueprintJSON Create in Port ``` { "identifier": "azureSubscription", "title": "Azure Subscription", "icon": "Azure", "schema": { "properties": { "tags": { "title": "Tags", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: subscription selector: query: 'true' apiVersion: '2022-09-01' port: entity: mappings: identifier: .id title: .display_name blueprint: '"azureSubscription"' properties: tags: .tags ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiVersion` | `string` | The Azure API version to use when querying this resource kind. | BlueprintJSON Create in Port ``` { "identifier": "azureResourceGroup", "description": "This blueprint represents an Azure Resource Group in our software catalog", "title": "Resource Group", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "provisioningState": { "title": "Provisioning State", "type": "string" }, "tags": { "title": "Tags", "type": "object" } } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "subscription": { "target": "azureSubscription", "title": "Subscription", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: Microsoft.Resources/resourceGroups selector: query: 'true' apiVersion: '2022-09-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureResourceGroup"' properties: location: .location provisioningState: .properties.provisioningState + .properties.provisioning_state tags: .tags relations: subscription: .id | split("/") | .[1] |= ascii_downcase |.[2] |= ascii_downcase | .[:3] |join("/") ``` BlueprintJSON Create in Port ``` { "identifier": "azureCloudResource", "title": "Cloud Resource", "icon": "Azure", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" }, "tags": { "title": "Tags", "type": "object", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: Microsoft.App/containerApps selector: query: 'true' apiVersion: '2022-03-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` BlueprintJSON Create in Port ``` { "identifier": "azureCloudResource", "title": "Cloud Resource", "icon": "Azure", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" }, "tags": { "title": "Tags", "type": "object", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: Microsoft.Storage/storageAccounts selector: query: 'true' apiVersion: '2023-01-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` BlueprintJSON Create in Port ``` { "identifier": "azureCloudResource", "title": "Cloud Resource", "icon": "Azure", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" }, "tags": { "title": "Tags", "type": "object", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: Microsoft.Compute/virtualMachines selector: query: 'true' apiVersion: '2023-03-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` BlueprintJSON Create in Port ``` { "identifier": "azureCloudResource", "title": "Cloud Resource", "icon": "Azure", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" }, "tags": { "title": "Tags", "type": "object", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: Microsoft.ContainerService/managedClusters selector: query: 'true' apiVersion: '2023-05-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` BlueprintJSON Create in Port ``` { "identifier": "azureCloudResource", "title": "Cloud Resource", "icon": "Azure", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" }, "tags": { "title": "Tags", "type": "object", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: Microsoft.Network/loadBalancers selector: query: 'true' apiVersion: '2023-02-01' port: entity: mappings: identifier: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## FAQ[​](#faq "Direct link to FAQ") 1What determines whether Port can ingest resources from an Azure subscription? 2Which Azure sync approach supports real-time updates? 3Can the Azure integration ingest resource types outside the default mapping? ## Next steps[​](#next-steps "Direct link to Next steps") * Refer to the [Resource Templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/.md) page for templates on how to map Azure resources to Port. * Check out the [Azure resource graph](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/azure-resource-graph/.md) guide for setting up synchronization of Azure resources. --- # Azure Resource Graph (Beta) Loading version... Availability Notice This integration is in closed beta and is not available for general use. Please contact [Port's support team](http://support.port.io/) to request access. This integration provides a robust solution for syncing your Azure resources to Port by leveraging the open-source [Ocean framework](https://ocean.port.io). It is designed for high-volume data ingestion across multiple subscriptions and efficiently queries the Azure Resource Graph API, ensuring high-performance data ingestion even in large-scale environments. Key advantages: * **Centralized Syncing**: Ingest resources from all your Azure subscriptions with a single deployment. * **High-Speed Ingestion**: Leverage Azure Resource Graph to query and sync up to 5000 subscriptions simultaneously for maximum performance. * **Customizable Mapping**: Take full control over which resource types are ingested and how they are mapped to your software catalog. On each run, the integration performs a full synchronization, so your software catalog always reflects the current state of your Azure resources. You can use declarative YAML mapping to transform raw data and model it according to your software catalog's structure. The integration is packaged as a Docker container and can be deployed in any environment that supports it, such as Kubernetes or your CI/CD pipeline. This gives you full control over its execution schedule and operational management. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The Azure Resource Graph integration supports the following `kinds`: * `Resources` - represents an Azure resource. By default, they're pulled from the `resources` table, which includes a wide array of Azure resources such as virtual machines, storage accounts, network interfaces, and more. You can override this by specifying another Azure Resource Graph table. To see all supported tables, refer to the [official documentation](https://learn.microsoft.com/en-us/azure/governance/resource-graph/reference/supported-tables-resources). * `ResourceContainers` - represents management groups, subscriptions, and resource groups, providing the hierarchical context for your Azure resources. * `Subscription` - represents subscriptions from the Azure Resource Manager API. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration you get after installing the Azure integration. **Default mapping configuration (click to expand)** ``` resources: - kind: subscription selector: query: 'true' port: entity: mappings: identifier: .subscriptionId title: .displayName blueprint: '"azureSubscription"' properties: tags: .tags state: .state subscriptionPolicies: .subscriptionPolicies - kind: resourceContainer selector: query: 'true' graphQuery: >- "resourcecontainers | where type =~'microsoft.resources/subscriptions/resourcegroups' | project id, type, name, location, tags, subscriptionId, resourceGroup | extend resourceGroup=tolower(resourceGroup) | extend type=tolower(type) | order by id" port: entity: mappings: identifier: .id | gsub(" ";"_") title: .name blueprint: '"azureResourceGroup"' properties: tags: .tags type: .type location: .location relations: subscription: ("/subscriptions/" + .subscriptionId) | gsub(" ";"_") - kind: resource selector: query: 'true' graphQuery: >- "resources | project id, type, name, location, tags, subscriptionId, resourceGroup | extend resourceGroup=tolower(resourceGroup) | extend type=tolower(type) | join kind=leftouter ( resourcecontainers | where type =~ 'microsoft.resources/subscriptions/resourcegroups' | project rgName=tolower(name), rgTags=tags, rgSubscriptionId=subscriptionId ) on $left.subscriptionId == $right.rgSubscriptionId and $left.resourceGroup == $right.rgName | project id, type, name, location, tags, subscriptionId, resourceGroup, rgTags | order by id" port: entity: mappings: identifier: .id | gsub(" ";"_") title: .name blueprint: '"azureResource"' properties: tags: .tags location: .location relations: resource_group: >- ("/subscriptions/" + .subscriptionId + "/resourceGroups/" + .resourceGroup) | gsub(" ";"_") ``` ### The `graphQuery` selector[​](#the-graphquery-selector "Direct link to the-graphquery-selector") The `graphQuery` selector is a powerful feature that allows you to specify a custom query to fetch the exact Azure resources you need. It uses the [**Kusto Query Language (KQL)**](https://learn.microsoft.com/en-us/azure/governance/resource-graph/concepts/query-language). With `graphQuery`, you can: * **Filter resources** based on their properties, tags, or any other attribute. * **Select specific properties** to reduce the amount of data ingested. * **Join different resource tables** to enrich the data. * **Perform aggregations** and other advanced operations. Here is an example of a broad query versus an optimized query: **Broad Query:** ``` resources | project id, type, name, location, tags, subscriptionId, resourceGroup | order by id ``` **Optimized Query:** ``` resources | where type in~ ('microsoft.compute/virtualmachines', 'microsoft.storage/storageaccounts') and tags.environment == 'production' | project id, type, name, location, tags, subscriptionId, resourceGroup | order by id ``` The optimized query fetches only virtual machines and storage accounts that have the `environment` tag set to `production`, which is much more efficient. Optimizing your `graphQuery` It is highly recommended to optimize your `graphQuery` to fetch only the data you need. A well-crafted query can significantly improve the performance of the integration and ensure that your software catalog is not cluttered with unnecessary information. For example, instead of fetching all resources and then filtering them in the mapping, you can use the `where` clause in your query to filter the resources at the source. ### Known limitations[​](#known-limitations "Direct link to Known limitations") The Azure Resource Graph API has undocumented behavior that can cause data inconsistencies when querying large datasets. We have identified two specific issues: 1. **Data inconsistency with multi-subscription queries** - When querying multiple Azure subscriptions simultaneously, subscriptions with a large number of resources may return inconsistent or incomplete data. 2. **Data drift during pagination** - If resources are created or deleted in Azure while the integration is paginating through results, the API may return newly created resources instead of existing ones on subsequent pages. This can cause entities to appear as "deleted" in Port when they still exist in Azure. Required: Add order by id to all queries Due to [known limitations](#known-limitations) in the Azure Resource Graph API, you should include `| order by id` at the end of all your `graphQuery` selectors. This ensures stable pagination and prevents data inconsistencies. #### Workaround[​](#workaround "Direct link to Workaround") To prevent these issues, **adding `| order by id` at the end of all your `graphQuery` selectors**. This ensures stable ordering during pagination and significantly reduces data inconsistencies. **Before (problematic):** ``` resources | where type in~ ('microsoft.compute/virtualmachines') | project id, type, name, location, tags, subscriptionId, resourceGroup ``` **After (recommended):** ``` resources | where type in~ ('microsoft.compute/virtualmachines') | project id, type, name, location, tags, subscriptionId, resourceGroup | order by id ``` ## Installation[​](#installation "Direct link to Installation") This integration supports multiple authentication methods through Azure's [`DefaultAzureCredential`](https://learn.microsoft.com/en-us/azure/developer/python/sdk/authentication/credential-chains?tabs=dac#defaultazurecredential-overview). Choose the method that best fits your deployment environment. 1Client credentials2Prerequisites3Set up authentication4Deploy integration Client credentials * ✧Uses an Azure App Registration with a client secret * ✧Works with any deployment method (Helm, Docker, CI/CD) * ✧Recommended for most environments Workload identity (OIDC) * ✧Uses AKS workload identity with OIDC federation * ✧No secrets to manage - short-lived tokens are rotated automatically * ✧Best for AKS (Kubernetes) deployments Managed identity * ✧Uses Azure managed identity on VMs or App Service * ✧No secrets to manage - Azure provides credentials automatically * ✧Best for Azure VMs and App Service deployments Prerequisites ## Examples[​](#examples "Direct link to Examples") ### Mapping Azure cloud resources[​](#mapping-azure-cloud-resources "Direct link to Mapping Azure cloud resources") The following example demonstrates how to ingest your Azure Subscriptions to Port.
You can use the following Port blueprint definitions and integration configuration: **Blueprint (click to expand)** Create in Port ``` { "identifier": "azureCloudResources", "description": "This blueprint represents an Azure Cloud Resource in our software catalog", "title": "Azure Cloud Resources", "icon": "Azure", "schema": { "properties": { "tags": { "title": "Tags", "type": "object" }, "type": { "title": "Type", "type": "string" }, "location": { "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Mapping configuration (click to expand)** ``` resources: - kind: resource selector: query: 'true' graphQuery: >- "resources | where type in~ ('microsoft.insights/datacollectionendpoints', 'microsoft.compute/virtualmachines') | project id, type, name, location, tags, subscriptionId, resourceGroup | extend resourceGroup=tolower(resourceGroup) | extend type=tolower(type) | order by id" port: entity: mappings: identifier: .id | gsub(" ";"_") title: .name blueprint: '"azureCloudResources"' properties: tags: .tags type: .type location: .location ``` ### Mapping cloud resources and resource groups[​](#mapping-cloud-resources-and-resource-groups "Direct link to Mapping cloud resources and resource groups") The following example demonstrates how to ingest your Azure Subscriptions to Port.
You can use the following Port blueprint definitions and integration configuration: **Blueprints (click to expand)** ``` [ { "identifier": "azureResourceGroup", "description": "This blueprint represents an Azure Resource Group in our software catalog", "title": "Azure Resource Group", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "tags": { "title": "Tags", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} }, { "identifier": "azureResource", "description": "This blueprint represents an AzureCloud Resource in our software catalog", "title": "Azure Cloud Resources", "icon": "Git", "schema": { "properties": { "tags": { "title": "Tags", "type": "object" }, "type": { "title": "Type", "type": "string" }, "location": { "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ] ``` **Mapping configuration (click to expand)** ``` resources: - kind: resourceContainer selector: query: 'true' graphQuery: >- "resourcecontainers | where type =~ 'microsoft.resources/subscriptions/resourcegroups' | where (tostring(tags['environment']) =~ 'prod') | project id, type, name, location, tags, subscriptionId, resourceGroup | extend resourceGroup=tolower(resourceGroup) | extend type=tolower(type) | order by id" port: entity: mappings: identifier: .id | gsub(" ";"_") title: .name blueprint: '"azureResourceGroup"' properties: tags: .tags location: .location - kind: resource selector: query: 'true' graphQuery: >- "resources | project id, type, name, location, tags, subscriptionId, resourceGroup | extend resourceGroup=tolower(resourceGroup) | extend type=tolower(type) | join kind=leftouter ( resourcecontainers | where type =~ 'microsoft.resources/subscriptions/resourcegroups' | project rgName=tolower(name), rgTags=tags, rgSubscriptionId=subscriptionId ) on $left.subscriptionId == $right.rgSubscriptionId and $left.resourceGroup == $right.rgName | where (tostring(rgTags['environment']) =~ 'prod') and not (tostring(rgTags['environment']) =~ 'staging') | project id, type, name, location, tags, subscriptionId, resourceGroup, rgTags | order by id" port: entity: mappings: identifier: .id | gsub(" ";"_") title: .name blueprint: '"azureResource"' properties: tags: .tags type: .type location: .location relations: resource_group: >- ("/subscriptions/" + .subscriptionId + "/resourceGroups/" + .resourceGroup) | gsub(" ";"_") ``` --- # Resource Templates This page contains the base examples for mapping Azure resources to Port. This base example thrives to provide a simpler and more abstract way to map Azure resources to Port. The simplification is achieved by using the generic `cloudResource` blueprint, which can be used to map any Azure resource to Port. ![Azure Basic Blueprints](/assets/images/basic-examples-blueprints-0300756d307e74b5d667da5e94dbbe2a.png) ## Mapping Azure Subscriptions[​](#mapping-azure-subscriptions "Direct link to Mapping Azure Subscriptions") The following example demonstrates how to ingest your Azure Subscriptions to Port.
You can use the following Port blueprint definitions and integration configuration: **Subscription Blueprint (click to expand)** Create in Port ``` { "identifier": "azureSubscription", "title": "Azure Subscription", "icon": "Azure", "schema": { "properties": { "tags": { "title": "Tags", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Mapping configuration for Azure Subscriptions (click to expand)** ``` resources: - kind: subscription selector: query: 'true' apiVersion: '2022-09-01' port: entity: mappings: identifier: .id title: .display_name blueprint: '"azureSubscription"' properties: { tags: .tags } ``` Here are the API references we used to create those blueprints and app config: * [Subscription](https://docs.microsoft.com/en-us/rest/api/resources/subscriptions/list) ## Mapping Resource Groups[​](#mapping-resource-groups "Direct link to Mapping Resource Groups") Relate subscriptions and resource groups The Resource Group has a relation to the Subscription, so the creation of the [Subscription blueprint](#mapping-azure-subscriptions) is required. The following example demonstrates how to ingest your Azure Resource Groups to Port.
You can use the following Port blueprint definitions and integration configuration: **Resource Group blueprint (click to expand)** Create in Port ``` { "identifier": "azureResourceGroup", "description": "This blueprint represents an Azure Resource Group in our software catalog", "title": "Resource Group", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "provisioningState": { "title": "Provisioning State", "type": "string" }, "tags": { "title": "Tags", "type": "object" } } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "subscription": { "target": "azureSubscription", "title": "Subscription", "required": false, "many": false } } } ``` **Mapping Configuration for Resource group (click to expand)** ``` resources: - kind: Microsoft.Resources/resourceGroups selector: query: 'true' apiVersion: '2022-09-01' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureResourceGroup"' properties: location: .location provisioningState: .properties.provisioningState + .properties.provisioning_state tags: .tags relations: subscription: >- .id | split("/") | .[1] |= ascii_downcase |.[2] |= ascii_downcase | .[:3] |join("/") ``` Here are the API references we used to create those blueprints and app config: * [Resource Group](https://docs.microsoft.com/en-us/rest/api/resources/resourcegroups/list) ## Mapping Cloud Resources[​](#mapping-cloud-resources "Direct link to Mapping Cloud Resources") The following example demonstrates how to ingest your Azure Resources to Port.
You can use the following Port blueprint definitions and integration configuration: Relate resources and resource groups The Resources below have a relation to the Resource Group, so the creation of the [Resource Group blueprint](#mapping-resource-groups) is required. **Cloud Resource Blueprint (click to expand)** Create in Port ``` { "identifier": "azureCloudResource", "title": "Cloud Resource", "icon": "Azure", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" }, "tags": { "title": "Tags", "type": "object", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` **Mapping configuration for cloud resources (click to expand)** Cloud resource kind The `cloudResource` kind is a generic kind that can be used to map most cloud resources. The mapping requires passing the resource kind and version as a parameter inside the `resourceKinds` object. It is possible that some of the kinds that you want to export are not in this example, head to the bottom of the page to see how to add them. ``` resources: - kind: Microsoft.App/containerApps selector: query: 'true' apiVersion: '2022-03-01' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.Storage/storageAccounts selector: query: 'true' apiVersion: '2023-01-01' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.Compute/virtualMachines selector: query: 'true' apiVersion: '2023-03-01' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.ContainerService/managedClusters selector: query: 'true' apiVersion: '2023-05-01' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.Network/loadBalancers selector: query: 'true' apiVersion: '2023-02-01' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureCloudResource"' properties: location: .location type: .type tags: .tags relations: resource_group: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` Here are the API references we used to create those blueprints and app config: * [AKS](https://learn.microsoft.com/en-us/rest/api/aks/managed-clusters/list?tabs=HTTP) * [Container App](https://learn.microsoft.com/en-us/rest/api/containerapps/stable/container-apps/list-by-subscription?tabs=HTTP) * [Load Balancer](https://learn.microsoft.com/en-us/rest/api/load-balancer/) * [Virtual Machine](https://learn.microsoft.com/en-us/rest/api/compute/virtual-machines/list-all?tabs=HTTP) * [Storage Account](https://docs.microsoft.com/en-us/rest/api/storagerp/storageaccounts/list) ## Mapping Extra Resources[​](#mapping-extra-resources "Direct link to Mapping Extra Resources") The resources in this page are only a few of the resources that the Azure Exporter supports. If the resources you want to ingest into Port do not appear in these examples, you can head to the [Mapping Extra Resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/mapping_extra_resources/.md) page to learn about all of the kinds of Azure resources that are supported by the Azure integration and how to map them into Port. ## Advanced Use Cases[​](#advanced-use-cases "Direct link to Advanced Use Cases") In certain scenarios you may want to model your Azure resources in a more detailed way. For example, you may want to model a Storage Account and its Containers separately. For these cases, head to the [Advanced Resource templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/advanced/.md) page to learn how to model your Azure resources in a more detailed way. --- # Advanced Templates ## Mapping storage resources[​](#mapping-storage-resources "Direct link to Mapping storage resources") The following example demonstrates how to ingest your Azure Storage Accounts and Containers to Port.
You can use the following Port blueprint definitions and integration configuration: Resource group requirement The Storage Account has a relation to the Resource Group, so creation of the [Resource Group blueprint](#mapping-resource-groups) is required. **Storage Account Blueprint (click to expand)** Create in Port ``` { "identifier": "azureStorageAccount", "description": "This blueprint represents an Azure Storage Account in our software catalog", "title": "Storage Account", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "provisioningState": { "title": "Provisioning State", "type": "string", "enum": ["Creating", "ResolvingDNS", "Succeeded"] }, "creationTime": { "title": "Creation Time", "type": "string", "format": "date-time" }, "isHnsEnabled": { "title": "Is HNS Enabled", "type": "boolean", "default": false }, "fileEncryptionEnabled": { "title": "File Encryption Enabled", "type": "boolean" }, "blobEncryptionEnabled": { "title": "Blob Encryption Enabled", "type": "boolean" }, "primaryLocation": { "title": "Primary Location", "type": "string" }, "secondaryLocation": { "title": "Secondary Location", "type": "string" }, "statusOfPrimary": { "title": "Status of Primary", "type": "string", "enum": ["available", "unavailable"], "enumColors": { "unavailable": "red", "available": "green" } }, "statusOfSecondary": { "title": "Status of Secondary", "type": "string", "enum": ["available", "unavailable"], "enumColors": { "unavailable": "red", "available": "green" } }, "tags": { "title": "Tags", "type": "object" }, "allowBlobPublicAccess": { "title": "Allow Blob Public Access", "type": "boolean" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "resourceGroup": { "target": "azureResourceGroup", "title": "Resource Group", "required": false, "many": false } } } ``` **Storage Container Blueprint (click to expand)** Create in Port ``` { "identifier": "azureStorageContainer", "description": "This blueprint represents an Azure Storage Container in our software catalog", "title": "Storage Container", "icon": "Azure", "schema": { "properties": { "publicAccess": { "title": "Public Access", "type": "string" }, "hasImmutabilityPolicy": { "title": "Has Immutability Policy", "type": "boolean" }, "hasLegalHold": { "title": "Has Legal Hold", "type": "boolean" }, "deleted": { "title": "Deleted", "type": "boolean" }, "deletedTime": { "title": "Deleted Time", "type": "string" }, "remainingRetentionDays": { "title": "Remaining Retention Days", "type": "number" }, "leaseStatus": { "title": "Lease Status", "type": "string" }, "leaseState": { "title": "Lease State", "type": "string" }, "defaultEncryptionScope": { "title": "Default Encryption Scope", "type": "string" }, "version": { "title": "Version", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "storageAccount": { "target": "azureStorageAccount", "title": "Storage Account", "required": false, "many": false } } } ``` **Mapping configuration for Storage Account and Storage Container (click to expand)** ``` resources: - kind: Microsoft.Storage/storageAccounts selector: query: "true" # azure resource api version to query apiVersion: "2023-01-01" port: entity: mappings: # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureStorageAccount"' properties: location: .location provisioningState: .properties.provisioningState creationTime: .properties.creationTime isHnsEnabled: .properties.isHnsEnabled fileEncryptionEnabled: .properties.encryption.services.file.enabled blobEncryptionEnabled: .properties.encryption.services.blob.enabled primaryLocation: .properties.primaryLocation secondaryLocation: .properties.secondaryLocation statusOfPrimary: .properties.statusOfPrimary statusOfSecondary: .properties.statusOfSecondary allowBlobPublicAccess: .properties.allowBlobPublicAccess tags: .tags relations: # resolve resource group id from storage account id resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Storage/storageAccounts/blobServices/containers selector: query: "true" # azure resource api version to query apiVersion: "2023-01-01" port: entity: mappings: identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureStorageContainer"' properties: publicAccess: .properties.publicAccess hasImmutabilityPolicy: .properties.hasImmutabilityPolicy hasLegalHold: .properties.hasLegalHold deleted: .properties.deleted deletedTime: .properties.deletedTime remainingRetentionDays: .properties.remainingRetentionDays leaseStatus: .properties.leaseStatus leaseState: .properties.leaseState defaultEncryptionScope: .properties.defaultEncryptionScope version: .properties.version relations: # resolve storage account id from container id storageAccount: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:-4] | join("/")' ``` Here are the API references we used to create those blueprints and app config: * [Storage Account](https://docs.microsoft.com/en-us/rest/api/storagerp/storageaccounts/list) * [Storage Container](https://learn.microsoft.com/en-us/rest/api/storagerp/blob-containers/list?tabs=HTTP) ## Mapping compute resources[​](#mapping-compute-resources "Direct link to Mapping compute resources") The following example demonstrates how to ingest your Azure Resources to Port.
You can use the following Port blueprint definitions and integration configuration: Resource group requirement The Resources below have a relation to the Resource Group, so creation of the [Resource Group blueprint](#mapping-resource-groups) is required. **AKS blueprint (click to expand)** Create in Port ``` { "identifier": "azureAks", "description": "This blueprint represents an Azure Kubernetes Service in our software catalog", "title": "AKS", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "provisioningState": { "title": "Provisioning State", "type": "string" }, "powerState": { "title": "Power State", "type": "string" }, "kubernetesVersion": { "title": "Kubernetes Version", "type": "string" }, "currentKubernetesVersion": { "title": "Current Kubernetes Version", "type": "string" }, "dnsPrefix": { "title": "DNS Prefix", "type": "string" }, "fqdn": { "title": "FQDN", "type": "string" }, "nodeResourceGroup": { "title": "Node Resource Group", "type": "string" }, "enableRBAC": { "title": "Enable RBAC", "type": "boolean" }, "supportPlan": { "title": "Support Plan", "type": "string" }, "networkPlugin": { "title": "Network Plugin", "type": "string" }, "podCIDR": { "title": "Pod CIDR", "type": "string" }, "serviceCIDR": { "title": "Service CIDR", "type": "string" }, "dnsServiceIp": { "title": "DNS Service IP", "type": "string" }, "outboundType": { "title": "Outbound Type", "type": "string" }, "loadBalancerSKU": { "title": "Load Balancer SKU", "type": "string" }, "maxAgentPools": { "title": "Max Agent Pools", "type": "number" }, "skuTier": { "title": "Tier", "type": "string", "enum": ["Free", "Paid", "Standard"] } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "resourceGroup": { "target": "azureResourceGroup", "title": "Resource Group", "required": false, "many": false } } } ``` **Container App blueprint (click to expand)** Create in Port ``` { "identifier": "azureContainerApp", "description": "This blueprint represents an Azure Container App in our software catalog", "title": "Container App", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "provisioningState": { "title": "Provisioning State", "type": "string", "enum": ["Canceled", "InProgress", "Succeeded", "Deleting", "Failed"] }, "outboundIpAddresses": { "title": "Outbound IP Addresses", "type": "array" }, "externalIngress": { "title": "External Ingress", "type": "boolean" }, "hostName": { "title": "Host Name", "type": "string" }, "minReplicas": { "title": "Min Replicas", "type": "number" }, "maxReplicas": { "title": "Max Replicas", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "resourceGroup": { "target": "azureResourceGroup", "title": "Resource Group", "required": false, "many": false } } } ``` **Load Balancer blueprint (click to expand)** Create in Port ``` { "identifier": "azureLoadBalancer", "description": "This blueprint represents an Azure Load Balancer in our software catalog", "title": "Load Balancer", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "tags": { "title": "Tags", "type": "object" }, "provisioningState": { "title": "Provisioning State", "type": "string", "enum": [ "Succeeded", "Updating", "Deleting", "Failed" ] }, "frontendIpResourceIds": { "title": "Frontend IP Resource IDs", "type": "array" }, "backendAddressPoolsResourceIds": { "title": "Backend Address Pools Resource IDs", "type": "array" }, "loadBalancingRulesResourceIds": { "title": "Load Balancing Rules Resource IDs", "type": "array" }, "probesResourceIds": { "title": "Probes Resource IDs", "type": "array" }, "inboundNatRulesResourceIds": { "title": "Inbound NAT Rules Resource IDs", "type": "array" }, "inboundNatPoolsResourceIds": { "title": "Inbound NAT Pools Resource IDs", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "resourceGroup": { "target": "azureResourceGroup", "title": "Resource Group", "required": false, "many": false } } }, ``` **Virtual Machine blueprint (click to expand)** Create in Port ``` { "identifier": "azureVirtualMachine", "description": "This blueprint represents an Azure Virtual Machine in our software catalog", "title": "Virtual Machine", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "provisioningState": { "title": "Provisioning State", "type": "string" }, "vmSize": { "title": "VM Size", "type": "string" }, "osDiskName": { "title": "OS Disk Name", "type": "string" }, "osDiskType": { "title": "OS Disk Type", "type": "string" }, "osDiskCaching": { "title": "OS Disk Caching", "type": "string" }, "osDiskSizeGB": { "title": "OS Disk Size GB", "type": "number" }, "osDiskCreateOption": { "title": "OS Disk Create Option", "type": "string" }, "networkInterfaceIds": { "title": "Network Interface IDs", "type": "array" }, "licenseType": { "title": "License Type", "type": "string" }, "vmOsProfile": { "title": "VM OS Profile", "type": "object" }, "vmHardwareProfile": { "title": "VM Hardware Profile", "type": "object" }, "vmStorageProfile": { "title": "VM Storage Profile", "type": "object" }, "tags": { "title": "Tags", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "resourceGroup": { "target": "azureResourceGroup", "title": "Resource Group", "required": false, "many": false } } } ``` **Web App blueprint (click to expand)** Create in Port ``` { "identifier": "azureWebApp", "description": "This blueprint represents an Azure Web App in our software catalog", "title": "Web App", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "state": { "title": "State", "type": "string" }, "enabled": { "title": "Enabled", "type": "boolean" }, "defaultHostName": { "title": "Default Host Name", "type": "string" }, "appServicePlanId": { "title": "App Service Plan ID", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "resourceGroup": { "target": "azureResourceGroup", "title": "Resource Group", "required": false, "many": false } } } ``` **Mapping Configuration for AKS, ContainerApp, LoadBalancer, VirtualMachine and WebApp (click to expand)** ``` resources: - kind: Microsoft.App/containerApps selector: query: "true" # azure resource api version to query apiVersion: "2023-05-01" port: entity: mappings: identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureContainerApp"' properties: location: .location provisioningState: .properties.provisioningState outboundIpAddresses: .properties.outboundIpAddresses externalIngress: .properties.configuration.ingress.external hostName: .properties.configuration.ingress.fqdn minReplicas: .properties.template.scale.minReplicas maxReplicas: .properties.template.scale.maxReplicas relations: resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.ContainerService/managedClusters selector: query: "true" # azure resource api version to query apiVersion: "2023-05-01" port: entity: mappings: identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureAks"' properties: location: .location provisioningState: .properties.provisioningState powerState: .properties.powerState.code kubernetesVersion: .properties.kubernetesVersion currentKubernetesVersion: .properties.currentKubernetesVersion dnsPrefix: .properties.dnsPrefix fqdn: .properties.fqdn nodeResourceGroup: .properties.nodeResourceGroup enableRBAC: .properties.enableRBAC supportPlan: .properties.supportPlan networkPlugin: .properties.networkProfile.networkPlugin podCIDR: .properties.networkProfile.podCidr serviceCIDR: .properties.networkProfile.serviceCidr dnsServiceIp: .properties.networkProfile.dnsServiceIP outboundType: .properties.networkProfile.outboundType loadBalancerSKU: .properties.networkProfile.loadBalancerSku maxAgentPools: .properties.maxAgentPools skuTier: .properties.sku.tier relations: resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Network/loadBalancers selector: query: "true" # azure resource api version to query apiVersion: "2023-05-01" port: entity: mappings: identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureLoadBalancer"' properties: location: .location provisioningState: .properties.provisioningState tags: .tags frontendIpResourceIds: .properties.frontendIPConfigurations[].id backendAddressPoolResourceIds: .properties.backendAddressPools[].id loadBalancingRulesResourceIds: .properties.loadBalancingRules[].id probesResourceIds: .properties.probes[].id inboundNatRulesResourceIds: .properties.inboundNatRules[].id inboundNatPoolsResourceIds: .properties.inboundNatPools[].id relations: resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Compute/virtualMachines selector: query: "true" apiVersion: "2023-03-01" port: entity: mappings: identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureVirtualMachine"' properties: location: .location provisioningState: .properties.provisioningState vmSize: .properties.hardwareProfile.vmSize osDiskName: .properties.storageProfile.osDisk.name osType: .properties.storageProfile.osDisk.osType osDiskCaching: .properties.storageProfile.osDisk.caching osDiskSizeGB: .properties.storageProfile.osDisk.diskSizeGB osDiskCreateOption: .properties.storageProfile.osDisk.createOption networkInterfaceIds: .properties.networkProfile.networkInterfaces[].id licenseType: .properties.licenseType vmOsProfile: .properties.osProfile vmHardwareProfile: .properties.hardwareProfile vmStorageProfile: .properties.storageProfile tags: .tags relations: resourceGroup: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Web/sites selector: query: "true" apiVersion: "2022-03-01" port: entity: mappings: identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureWebApp"' properties: location: .location state: .properties.state enabled: .properties.enabled defaultHostName: .properties.defaultHostName appServicePlanId: .properties.serverFarmId relations: resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' ``` Here are the API references we used to create those blueprints and app config: * [AKS](https://learn.microsoft.com/en-us/rest/api/aks/managed-clusters/list?tabs=HTTP) * [Container App](https://learn.microsoft.com/en-us/rest/api/containerapps/stable/container-apps/list-by-subscription?tabs=HTTP) * [Load Balancer](https://learn.microsoft.com/en-us/rest/api/load-balancer/) * [Virtual Machine](https://learn.microsoft.com/en-us/rest/api/compute/virtual-machines/list-all?tabs=HTTP) * [Web App](https://learn.microsoft.com/en-us/rest/api/appservice/web-apps/list) ## Mapping database resources[​](#mapping-database-resources "Direct link to Mapping database resources") The following example demonstrates how to ingest your Azure Database Resources to Port.
You can use the following Port blueprint definitions and integration configuration: Resource group requirement The Database Resources below have a relation to the Resource Group, so creation of the [Resource Group blueprint](#mapping-resource-groups) is required. **Postgres Flexible Server blueprint (click to expand)** Create in Port ``` { "identifier": "azurePostgresFlexibleServer", "description": "This blueprint represents an Azure Postgres Flexible Server in our software catalog", "title": "Postgres Flexible Server", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "version": { "title": "Version", "type": "string" }, "state": { "title": "State", "type": "string" }, "storageSizeGB": { "title": "Storage Size (GB)", "type": "number" }, "activeDirectoryAuth": { "title": "Active Directory Authentication", "type": "string", "enum": ["Enabled", "Disabled"] }, "passwordAuth": { "title": "Password Authentication", "type": "string", "enum": ["Enabled", "Disabled"] }, "publicNetworkAccess": { "title": "Public Network Access", "type": "string", "enum": ["Enabled", "Disabled"] }, "geoRedundantBackup": { "title": "Geo-Redundant Backup", "type": "string", "enum": ["Enabled", "Disabled"] }, "earliestRestoreDate": { "title": "Earliest Restore Date", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "resourceGroup": { "target": "azureResourceGroup", "title": "Resource Group", "required": false, "many": false } } } ``` **Mapping Configuration for Postgres Flexible Server (click to expand)** ``` resources: - kind: Microsoft.DBforPostgreSQL/flexibleServers selector: query: "true" apiVersion: "2022-12-01" port: entity: mappings: identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azurePostgresFlexibleServer"' properties: location: .location version: .properties.version state: .properties.state storageSizeGB: .properties.storage.storageSizeGB activeDirectoryAuth: .properties.authConfig.activeDirectoryAuth passwordAuth: .properties.authConfig.passwordAuth publicNetworkAccess: .properties.network.publicNetworkAccess geoRedundantBackup: .properties.backup.geoRedundantBackup earliestRestoreDate: .properties.backup.earliestRestoreDate relations: resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' ``` Here are the API references we used to create those blueprints and app config: * [Postgres Flexible Server](https://docs.microsoft.com/en-us/rest/api/azure-postgresql/flexibleservers) ## Mapping service bus resources[​](#mapping-service-bus-resources "Direct link to Mapping service bus resources") The following example demonstrates how to ingest your Azure Service Bus resources (Namespaces, Queues, Topics, and Subscriptions) to Port.
You can use the following Port blueprint definitions and integration configuration: Hierarchical relationships The Service Bus resources have a hierarchical relationship. The Namespace relates to the Resource Group, Queues and Topics relate to the Namespace, and Subscriptions relate to Topics. Creation of the [Resource Group blueprint](#mapping-resource-groups) is required. **Service Bus Namespace blueprint (click to expand)** Create in Port ``` { "identifier": "azureServiceBusNamespace", "title": "Service Bus Namespace", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string", "icon": "Home" }, "type": { "title": "Type", "type": "string", "icon": "Microservice" }, "sku": { "title": "SKU", "type": "object", "icon": "Settings" }, "provisioningState": { "title": "Provisioning State", "type": "string" }, "status": { "title": "Status", "type": "string" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "serviceBusEndpoint": { "title": "Service Bus Endpoint", "type": "string", "format": "url" }, "metricId": { "title": "Metric ID", "type": "string" }, "tags": { "title": "Tags", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` **Service Bus Queue blueprint (click to expand)** Create in Port ``` { "identifier": "azureServiceBusQueue", "title": "Service Bus Queue", "icon": "Azure", "schema": { "properties": { "type": { "title": "Type", "type": "string" }, "messageCount": { "title": "Message Count", "type": "number" }, "sizeInBytes": { "title": "Size in Bytes", "type": "number" }, "maxSizeInMegabytes": { "title": "Max Size in MB", "type": "number" }, "maxDeliveryCount": { "title": "Max Delivery Count", "type": "number" }, "lockDuration": { "title": "Lock Duration", "type": "string" }, "status": { "title": "Status", "type": "string" }, "enablePartitioning": { "title": "Enable Partitioning", "type": "boolean" }, "enableExpress": { "title": "Enable Express", "type": "boolean" }, "enableBatchedOperations": { "title": "Enable Batched Operations", "type": "boolean" }, "requiresDuplicateDetection": { "title": "Requires Duplicate Detection", "type": "boolean" }, "requiresSession": { "title": "Requires Session", "type": "boolean" }, "supportOrdering": { "title": "Support Ordering", "type": "boolean" }, "defaultMessageTimeToLive": { "title": "Default Message TTL", "type": "string" }, "deadLetteringOnMessageExpiration": { "title": "Dead Lettering On Message Expiration", "type": "boolean" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "accessedAt": { "title": "Accessed At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "namespace": { "title": "Namespace", "target": "azureServiceBusNamespace", "required": false, "many": false } } } ``` **Service Bus Topic blueprint (click to expand)** Create in Port ``` { "identifier": "azureServiceBusTopic", "title": "Service Bus Topic", "icon": "Azure", "schema": { "properties": { "type": { "title": "Type", "type": "string" }, "subscriptionCount": { "title": "Subscription Count", "type": "number" }, "sizeInBytes": { "title": "Size in Bytes", "type": "number" }, "maxSizeInMegabytes": { "title": "Max Size in MB", "type": "number" }, "status": { "title": "Status", "type": "string" }, "enablePartitioning": { "title": "Enable Partitioning", "type": "boolean" }, "enableBatchedOperations": { "title": "Enable Batched Operations", "type": "boolean" }, "requiresDuplicateDetection": { "title": "Requires Duplicate Detection", "type": "boolean" }, "supportOrdering": { "title": "Support Ordering", "type": "boolean" }, "defaultMessageTimeToLive": { "title": "Default Message TTL", "type": "string" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "accessedAt": { "title": "Accessed At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "namespace": { "title": "Namespace", "target": "azureServiceBusNamespace", "required": false, "many": false } } } ``` **Service Bus Subscription blueprint (click to expand)** Create in Port ``` { "identifier": "azureServiceBusSubscription", "title": "Service Bus Subscription", "icon": "Azure", "schema": { "properties": { "type": { "title": "Type", "type": "string" }, "messageCount": { "title": "Message Count", "type": "number" }, "maxDeliveryCount": { "title": "Max Delivery Count", "type": "number" }, "lockDuration": { "title": "Lock Duration", "type": "string" }, "status": { "title": "Status", "type": "string" }, "enableBatchedOperations": { "title": "Enable Batched Operations", "type": "boolean" }, "requiresSession": { "title": "Requires Session", "type": "boolean" }, "defaultMessageTimeToLive": { "title": "Default Message TTL", "type": "string" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "accessedAt": { "title": "Accessed At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "topic": { "title": "Topic", "target": "azureServiceBusTopic", "required": false, "many": false } } } ``` **Mapping Configuration for Service Bus resources (click to expand)** ``` resources: - kind: Microsoft.ServiceBus/namespaces selector: query: 'true' apiVersion: '2022-10-01-preview' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureServiceBusNamespace"' properties: location: .location type: .type sku: .sku provisioningState: .properties.provisioningState status: .properties.status createdAt: .properties.createdAt updatedAt: .properties.updatedAt serviceBusEndpoint: .properties.serviceBusEndpoint metricId: .properties.metricId tags: .tags relations: resource_group: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") - kind: Microsoft.ServiceBus/namespaces/queues selector: query: 'true' apiVersion: '2022-10-01-preview' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureServiceBusQueue"' properties: type: .type createdAt: .properties.createdAt updatedAt: .properties.updatedAt accessedAt: .properties.accessedAt sizeInBytes: .properties.sizeInBytes messageCount: .properties.messageCount lockDuration: .properties.lockDuration maxSizeInMegabytes: .properties.maxSizeInMegabytes maxDeliveryCount: .properties.maxDeliveryCount enableBatchedOperations: .properties.enableBatchedOperations status: .properties.status enablePartitioning: .properties.enablePartitioning enableExpress: .properties.enableExpress requiresDuplicateDetection: .properties.requiresDuplicateDetection requiresSession: .properties.requiresSession defaultMessageTimeToLive: .properties.defaultMessageTimeToLive deadLetteringOnMessageExpiration: .properties.deadLetteringOnMessageExpiration supportOrdering: .properties.supportOrdering relations: namespace: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:7] |join("/") - kind: Microsoft.ServiceBus/namespaces/topics selector: query: 'true' apiVersion: '2022-10-01-preview' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureServiceBusTopic"' properties: type: .type createdAt: .properties.createdAt updatedAt: .properties.updatedAt accessedAt: .properties.accessedAt sizeInBytes: .properties.sizeInBytes subscriptionCount: .properties.subscriptionCount maxSizeInMegabytes: .properties.maxSizeInMegabytes enableBatchedOperations: .properties.enableBatchedOperations status: .properties.status enablePartitioning: .properties.enablePartitioning supportOrdering: .properties.supportOrdering requiresDuplicateDetection: .properties.requiresDuplicateDetection defaultMessageTimeToLive: .properties.defaultMessageTimeToLive relations: namespace: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:7] |join("/") - kind: Microsoft.ServiceBus/namespaces/topics/subscriptions selector: query: 'true' apiVersion: '2022-10-01-preview' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureServiceBusSubscription"' properties: type: .type createdAt: .properties.createdAt updatedAt: .properties.updatedAt accessedAt: .properties.accessedAt messageCount: .properties.messageCount maxDeliveryCount: .properties.maxDeliveryCount lockDuration: .properties.lockDuration enableBatchedOperations: .properties.enableBatchedOperations status: .properties.status requiresSession: .properties.requiresSession defaultMessageTimeToLive: .properties.defaultMessageTimeToLive relations: topic: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:9] |join("/") ``` Here are the API references we used to create those blueprints and app config: * [Service Bus Namespace](https://learn.microsoft.com/en-us/rest/api/servicebus/controlplane/namespaces/list?view=rest-servicebus-controlplane-2024-01-01\&tabs=HTTP) * [Service Bus Queue](https://learn.microsoft.com/en-us/rest/api/servicebus/controlplane/queues/list-by-namespace?view=rest-servicebus-controlplane-2024-01-01\&tabs=HTTP) * [Service Bus Topic](https://learn.microsoft.com/en-us/rest/api/servicebus/controlplane/topics/list-by-namespace?view=rest-servicebus-controlplane-2024-01-01\&tabs=HTTP) * [Service Bus Subscription](https://learn.microsoft.com/en-us/rest/api/servicebus/controlplane/subscriptions/list-by-topic?view=rest-servicebus-controlplane-2024-01-01\&tabs=HTTP) ## Mapping application insights[​](#mapping-application-insights "Direct link to Mapping application insights") The following example demonstrates how to ingest your Azure Application Insights components to Port.
You can use the following Port blueprint definitions and integration configuration: Resource group requirement Application Insights has a relation to the Resource Group, so creation of the [Resource Group blueprint](#mapping-resource-groups) is required. **Application Insights blueprint (click to expand)** Create in Port ``` { "identifier": "azureApplicationInsights", "title": "Application Insights", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string", "icon": "Home" }, "type": { "title": "Type", "type": "string", "icon": "Microservice" }, "kind": { "title": "Kind", "type": "string" }, "applicationType": { "title": "Application Type", "type": "string" }, "applicationId": { "title": "Application ID", "type": "string" }, "instrumentationKey": { "title": "Instrumentation Key", "type": "string", "icon": "Lock" }, "connectionString": { "title": "Connection String", "type": "string" }, "provisioningState": { "title": "Provisioning State", "type": "string" }, "retentionInDays": { "title": "Retention In Days", "type": "number" }, "samplingPercentage": { "title": "Sampling Percentage", "type": "number" }, "publicNetworkAccessForIngestion": { "title": "Public Network Access For Ingestion", "type": "string" }, "publicNetworkAccessForQuery": { "title": "Public Network Access For Query", "type": "string" }, "tags": { "title": "Tags", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` **Mapping Configuration for Application Insights (click to expand)** ``` resources: - kind: Microsoft.Insights/components selector: query: 'true' apiVersion: '2020-02-02' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureApplicationInsights"' properties: location: .location type: .type kind: .kind applicationType: .properties.Application_Type applicationId: .properties.AppId instrumentationKey: .properties.InstrumentationKey connectionString: .properties.ConnectionString provisioningState: .properties.provisioningState flowType: .properties.Flow_Type requestSource: .properties.Request_Source retentionInDays: .properties.RetentionInDays samplingPercentage: .properties.SamplingPercentage publicNetworkAccessForIngestion: .properties.publicNetworkAccessForIngestion publicNetworkAccessForQuery: .properties.publicNetworkAccessForQuery tags: .tags relations: resource_group: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` Here are the API references we used to create those blueprints and app config: * [Application Insights](https://learn.microsoft.com/en-us/rest/api/application-insights/components/list) ## Mapping key vault[​](#mapping-key-vault "Direct link to Mapping key vault") The following example demonstrates how to ingest your Azure Key Vaults to Port.
You can use the following Port blueprint definitions and integration configuration: Resource group requirement Key Vault has a relation to the Resource Group, so creation of the [Resource Group blueprint](#mapping-resource-groups) is required. **Key Vault blueprint (click to expand)** Create in Port ``` { "identifier": "azureKeyVault", "title": "Key Vault", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string", "icon": "Home" }, "type": { "title": "Type", "type": "string", "icon": "Microservice" }, "tenantId": { "title": "Tenant ID", "type": "string" }, "sku": { "title": "SKU", "type": "object", "icon": "Settings" }, "vaultUri": { "title": "Vault URI", "type": "string", "format": "url", "icon": "Link" }, "provisioningState": { "title": "Provisioning State", "type": "string" }, "enabledForDeployment": { "title": "Enabled For Deployment", "type": "boolean" }, "enabledForDiskEncryption": { "title": "Enabled For Disk Encryption", "type": "boolean" }, "enabledForTemplateDeployment": { "title": "Enabled For Template Deployment", "type": "boolean" }, "enableSoftDelete": { "title": "Enable Soft Delete", "type": "boolean" }, "softDeleteRetentionInDays": { "title": "Soft Delete Retention In Days", "type": "number" }, "enableRbacAuthorization": { "title": "Enable RBAC Authorization", "type": "boolean" }, "enablePurgeProtection": { "title": "Enable Purge Protection", "type": "boolean" }, "publicNetworkAccess": { "title": "Public Network Access", "type": "string" }, "networkAcls": { "title": "Network ACLs", "type": "object" }, "tags": { "title": "Tags", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "resource_group": { "title": "Resource Group", "target": "azureResourceGroup", "required": false, "many": false } } } ``` **Mapping Configuration for Key Vault (click to expand)** ``` resources: - kind: Microsoft.KeyVault/vaults selector: query: 'true' apiVersion: '2023-07-01' port: entity: mappings: identifier: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/") title: .name blueprint: '"azureKeyVault"' properties: location: .location type: .type tenantId: .properties.tenantId sku: .properties.sku vaultUri: .properties.vaultUri provisioningState: .properties.provisioningState enabledForDeployment: .properties.enabledForDeployment enabledForDiskEncryption: .properties.enabledForDiskEncryption enabledForTemplateDeployment: .properties.enabledForTemplateDeployment enableSoftDelete: .properties.enableSoftDelete softDeleteRetentionInDays: .properties.softDeleteRetentionInDays enableRbacAuthorization: .properties.enableRbacAuthorization enablePurgeProtection: .properties.enablePurgeProtection publicNetworkAccess: .properties.publicNetworkAccess networkAcls: .properties.networkAcls tags: .tags relations: resource_group: >- .id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/") ``` Here are the API references we used to create those blueprints and app config: * [Key Vault](https://learn.microsoft.com/en-us/rest/api/keyvault/keyvault/vaults/list-by-subscription?view=rest-keyvault-keyvault-2024-11-01\&tabs=HTTP) Mapping extra resources The resources in this page are only few of the resources that the Azure Exporter supports. If you don't find the Azure resource you want to map to Port head to the [Mapping Extra Resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/mapping_extra_resources/.md) page to learn about what kind of azure resources are supported by the Azure integration and how to map them into Port. --- # Mapping Extra Resources As you've probably looked at the [Resource Templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/.md) page, you've noticed that the Azure Exporter supports many Azure resources, but not all of them are documented in the Resource Templates page. This page will help you understand what kind of Azure resources are supported by the Azure integration and how to map them into Port. ## Is the resource supported by the Azure Exporter?[​](#is-the-resource-supported-by-the-azure-exporter "Direct link to Is the resource supported by the Azure Exporter?") The Azure Exporter relies on the Base Azure Resource to have a `List` API by `subscription` to get all the resources of a specific type. If it is a resource that is not documented in the [Resource Templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/resource_templates/.md) page, you can map it to Port by following these steps: 1. Find the resource in the [Azure REST API reference](https://learn.microsoft.com/en-us/rest/api/azure/). 2. If it's a base resource (in the format of `Microsoft./`) can be found in the type example response and in the API URL ![Azure REST API URL with storage resource type](/img/integrations/azure-exporter/StorageAccountTypeInAPIURL.png)

* If it has a List API by subscription it will be supported by the Azure Exporter. ![Azure Storage Accounts List API page](/img/integrations/azure-exporter/StorageAccountListAPIIsValid.png)

3. If it's an extension resource (in the format of `Microsoft.//`) such as `Microsoft.Storage/storageAccounts/blobServices/containers`. * Then, if the base resource is supported by the Azure Exporter, the extension resource will be supported as well. ### Example supported resource types[​](#example-supported-resource-types "Direct link to Example supported resource types") Azure Policy resources that you can map include: * `Microsoft.Authorization/policyDefinitions`. * `Microsoft.Authorization/policySetDefinitions`. * `Microsoft.Authorization/policyAssignments`. Filtering happens after resources are fetched The Azure integration does not support Azure-side filtering. It fetches every resource returned by the subscription-level `List` API, then applies the `selector.query` filter from your mapping configuration. For example, syncing `Microsoft.Authorization/policyDefinitions` fetches all approximately 1,000 Microsoft built-in policy definitions during each sync. Filter these resources in your mapping configuration after the integration fetches them. ## Mapping the resource to Port[​](#mapping-the-resource-to-port "Direct link to Mapping the resource to Port") After you've found the resource in the Azure REST API reference and you've confirmed that it is supported by the Azure Exporter, you can map it to Port by following these steps: ### Blueprint[​](#blueprint "Direct link to Blueprint") Create a Port blueprint definition for the resource. The blueprint definition is based on the resource API response in the Azure REST API reference. **Azure Storage Account Rest API Response Example (click to expand)** ``` { "value": [ { "id": "/subscriptions/{subscription-id}/resourceGroups/res2627/providers/Microsoft.Storage/storageAccounts/sto1125", "kind": "Storage", "location": "eastus", "name": "sto1125", "properties": { "isHnsEnabled": true, "creationTime": "2017-05-24T13:28:53.4540398Z", "primaryEndpoints": { "web": "https://sto1125.web.core.windows.net/", "dfs": "https://sto1125.dfs.core.windows.net/", "blob": "https://sto1125.blob.core.windows.net/", "file": "https://sto1125.file.core.windows.net/", "queue": "https://sto1125.queue.core.windows.net/", "table": "https://sto1125.table.core.windows.net/", "microsoftEndpoints": { "web": "https://sto1125-microsoftrouting.web.core.windows.net/", "dfs": "https://sto1125-microsoftrouting.dfs.core.windows.net/", "blob": "https://sto1125-microsoftrouting.blob.core.windows.net/", "file": "https://sto1125-microsoftrouting.file.core.windows.net/", "queue": "https://sto1125-microsoftrouting.queue.core.windows.net/", "table": "https://sto1125-microsoftrouting.table.core.windows.net/" }, "internetEndpoints": { "web": "https://sto1125-internetrouting.web.core.windows.net/", "dfs": "https://sto1125-internetrouting.dfs.core.windows.net/", "blob": "https://sto1125-internetrouting.blob.core.windows.net/", "file": "https://sto1125-internetrouting.file.core.windows.net/" } }, "primaryLocation": "eastus", "provisioningState": "Succeeded", "routingPreference": { "routingChoice": "MicrosoftRouting", "publishMicrosoftEndpoints": true, "publishInternetEndpoints": true }, "encryption": { "services": { "file": { "keyType": "Account", "enabled": true, "lastEnabledTime": "2019-12-11T20:49:31.7036140Z" }, "blob": { "keyType": "Account", "enabled": true, "lastEnabledTime": "2019-12-11T20:49:31.7036140Z" } }, "keySource": "Microsoft.Storage" }, "secondaryLocation": "centraluseuap", "statusOfPrimary": "available", "statusOfSecondary": "available", "supportsHttpsTrafficOnly": false }, "sku": { "name": "Standard_GRS", "tier": "Standard" }, "tags": { "key1": "value1", "key2": "value2" }, "type": "Microsoft.Storage/storageAccounts" } ] } ``` Based on the API response, you can create a Port blueprint definition for the resource. **Storage Account Blueprint (click to expand)** Create in Port ``` { "identifier": "azureStorageAccount", "description": "This blueprint represents an Azure Storage Account in our software catalog", "title": "Storage Account", "icon": "Azure", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "provisioningState": { "title": "Provisioning State", "type": "string", "enum": ["Creating", "ResolvingDNS", "Succeeded"] }, "creationTime": { "title": "Creation Time", "type": "string", "format": "date-time" }, "isHnsEnabled": { "title": "Is HNS Enabled", "type": "boolean", "default": false }, "fileEncryptionEnabled": { "title": "File Encryption Enabled", "type": "boolean" }, "blobEncryptionEnabled": { "title": "Blob Encryption Enabled", "type": "boolean" }, "primaryLocation": { "title": "Primary Location", "type": "string" }, "secondaryLocation": { "title": "Secondary Location", "type": "string" }, "statusOfPrimary": { "title": "Status of Primary", "type": "string", "enum": ["available", "unavailable"], "enumColors": { "unavailable": "red", "available": "green" } }, "statusOfSecondary": { "title": "Status of Secondary", "type": "string", "enum": ["available", "unavailable"], "enumColors": { "unavailable": "red", "available": "green" } }, "tags": { "title": "Tags", "type": "object" }, "allowBlobPublicAccess": { "title": "Allow Blob Public Access", "type": "boolean" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "resourceGroup": { "target": "azureResourceGroup", "title": "Resource Group", "required": false, "many": false } } } ``` ### Integration configuration[​](#integration-configuration "Direct link to Integration configuration") Create an integration configuration for the resource. The integration configuration is a YAML file that describes the ETL process to load data into the developer portal. **Mapping configuration for Storage Account and Storage Container (click to expand)** ``` resources: - kind: Microsoft.Storage/storageAccounts selector: query: "true" # azure resource api version to query apiVersion: "2023-01-01" port: entity: mappings: # lowercase only the resourceGroups namespace and name to align how azure API returns the resource group reference identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureStorageAccount"' properties: location: .location provisioningState: .properties.provisioningState creationTime: .properties.creationTime isHnsEnabled: .properties.isHnsEnabled fileEncryptionEnabled: .properties.encryption.services.file.enabled blobEncryptionEnabled: .properties.encryption.services.blob.enabled primaryLocation: .properties.primaryLocation secondaryLocation: .properties.secondaryLocation statusOfPrimary: .properties.statusOfPrimary statusOfSecondary: .properties.statusOfSecondary allowBlobPublicAccess: .properties.allowBlobPublicAccess tags: .tags relations: # resolve resource group id from storage account id resourceGroup: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | .[:5] |join("/")' - kind: Microsoft.Storage/storageAccounts/blobServices/containers selector: query: "true" # azure resource api version to query apiVersion: "2023-01-01" port: entity: mappings: identifier: '.id | split("/") | .[3] |= ascii_downcase |.[4] |= ascii_downcase | join("/")' title: .name blueprint: '"azureStorageContainer"' properties: publicAccess: .properties.publicAccess hasImmutabilityPolicy: .properties.hasImmutabilityPolicy hasLegalHold: .properties.hasLegalHold deleted: .properties.deleted deletedTime: .properties.deletedTime remainingRetentionDays: .properties.remainingRetentionDays leaseStatus: .properties.leaseStatus leaseState: .properties.leaseState defaultEncryptionScope: .properties.defaultEncryptionScope version: .properties.version relations: # resolve storage account id from container id storageAccount: '.id | split("/") | .[3] |= ascii_downcase | .[4] |= ascii_downcase | .[:-4] | join("/")' ``` #### The integration configuration structure[​](#the-integration-configuration-structure "Direct link to The integration configuration structure") * The `kind` field describes the Azure resource type to be ingested into Port. The `kind` field should be set to the Azure resource type as it appears in the [resource guide](https://learn.microsoft.com/en-us/azure/templates/). e.g. The resource type for the `Storage Account` could be found [here](https://learn.microsoft.com/en-us/azure/templates/microsoft.storage/storageaccounts?pivots=deployment-language-arm-template) as well with the resource object structure. ``` resources: - kind: Microsoft.Storage/storageAccounts selector: ... ``` * The `selector` field describes the Azure resource selection criteria. ``` resources: - kind: Microsoft.Storage/storageAccounts selector: query: "true" # JQ boolean expression. If evaluated to false - this object will be skipped. apiVersion: "2023-01-01" # Azure API version to use to fetch the resource port: ``` * The `query` field is a [JQ boolean query](https://stedolan.github.io/jq/manual/#Basicfilters), if evaluated to `false` - the resource will be skipped. Example use case - skip syncing resources that are not in a specific region. ``` query: .location == "eastus2" ``` * The `apiVersion` field is the Azure API version to use to fetch the resource. This field is required for all resources. For example, the API versions for the `storageAccount` resource was found in the [Storage Account Rest API](https://docs.microsoft.com/en-us/rest/api/storagerp/storageaccounts/list) ``` apiVersion: "2023-01-01" ``` ![Azure Storage Accounts API version highlighted](/img/integrations/azure-exporter/StorageAccountAPIVersion.png)

* The `port` field describes the Port entity to be created from the Azure resource. ``` resources: - kind: Microsoft.Storage/storageAccounts selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. apiVersion: "2023-01-01" # Azure API version to use to fetch the resource port: entity: mappings: # Mappings between one Azure object to a Port entity. Each value is a JQ query. identifier: .id title: .name blueprint: '"azureStorageAccount"' properties: location: .location provisioningState: .properties.provisioningState ``` * The `entity` field describes the Port entity to be created from the Azure resource. * The `mappings` field describes the mapping between the Azure resource and the Port entity. * The `identifier` field describes the Azure resource identifier. This field is required for all resources. ``` mappings: identifier: .id ``` * The `title` field describes the Azure resource title. This field is required for all resources. ``` mappings: title: .name ``` * The `blueprint` field describes the Port blueprint to be used to create the Port entity. This field is required for all resources. ``` mappings: blueprint: '"azureStorageAccount"' ``` * The `properties` field describes the Azure resource properties to be mapped to the Port ``` mappings: identifier: .id title: .name blueprint: '"azureStorageAccount"' properties: location: .location provisioningState: .properties.provisioningState ``` --- # GCP ![GCP icon](/img/guides/icons/GCP.svg)![GCP icon](/img/guides/icons/GCP.svg) Loading version... Our GCP integration allows you to import your Google Cloud resources into Port, according to your configuration. After the initial import of data, the integration will also listen to live events from Google Cloud to update data in Port in real time. Supported resources ![External link icon](/img/icons/external-link.svg)[cloudResource](https://docs.cloud.google.com/asset-inventory/docs/supported-asset-types)[cloudresourcemanager.googleapis.com/Project](https://docs.cloud.google.com/resource-manager/reference/rest/v3/projects)[cloudresourcemanager.googleapis.com/Organization](https://docs.cloud.google.com/resource-manager/reference/rest/v3/organizations)[cloudresourcemanager.googleapis.com/Folder](https://docs.cloud.google.com/resource-manager/reference/rest/v3/folders)[gcpCloudFunction](https://cloud.google.com/functions/docs/reference/rest/v2/projects.locations.functions)[container.googleapis.com/Cluster](https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.locations.clusters)[cloudfunctions.googleapis.com/CloudFunction](https://cloud.google.com/functions/docs/reference/rest/v2/projects.locations.functions)[pubsub.googleapis.com/Topic](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics)[compute.googleapis.com/Disk](https://cloud.google.com/compute/docs/reference/rest/v1/disks)[pubsub.googleapis.com/Subscription](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions)[iam.googleapis.com/ServiceAccount](https://cloud.google.com/iam/docs/reference/rest/v1/projects.serviceAccounts) ## Core support [Live eventsReal-time via Terraform deploymentSee supported resources](#setup) Open source Port's Google Cloud integration is open source, view the source code [**here**](https://github.com/port-labs/ocean/tree/main/integrations/gcp). ## Common use cases[​](#common-use-cases "Direct link to Common use cases") Easily fill your software catalog with data directly from your GCP Organization, for example: * Map all the resources in your Google Cloud Projects, including **Container Clusters**, **Cloud Run Services**, **BigQuery Tables**, **Compute engine Autoscaler** and other GCP objects. * Watch for Google Cloud object changes (create/update/delete) in real-time, and automatically apply the changes to your entities in Port. * Use relations to create complete, easily digestible views of your Google Cloud infrastructure inside Port. ## How it works[​](#how-it-works "Direct link to How it works") Port's Google Cloud integration can retrieve all the resources supported by the [Cloud Assets API](https://cloud.google.com/asset-inventory/docs/supported-asset-types), and export them to Port as entities of existing blueprints. The GCP integration allows you to perform extract, transform, load (ETL) on data from the Cloud Assets API into the desired software catalog data model. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). First time installation For your first deployment of the GCP exporter, we recommend starting with the Helm/scheduled installation method to perform the initial data sync. Once the initial data sync is complete, you can switch to the Terraform deployment method for real-time data sync. 1Helm2Prerequisites3Workload Identity4Google Kubernetes Engine5Install Helm (scheduled) * ✧Deploy on Kubernetes using Helm * ✧Multiple authentication methods (Workload Identity, Service Account Key) * ✧Scheduled resyncs of resources from GCP to Port CI/CD (scheduled) * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Docker (one-time) * ✧Run as a Docker container on any platform * ✧One-time or on-demand execution * ✧Minimal setup requirements Terraform (real-time) * ✧Deploy using Infrastructure as Code * ✧Real-time updates via event-based triggers * ✧Reproducible and version-controlled deployments Prerequisites ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration you get after installing the GCP integration. **Default mapping configuration (click to expand)** ``` resources: - kind: cloudresourcemanager.googleapis.com/Project selector: query: 'true' port: entity: mappings: identifier: .name blueprint: '"gcpProject"' title: .display_name properties: labels: .labels - kind: container.googleapis.com/Cluster selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name - kind: cloudfunctions.googleapis.com/CloudFunction selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name - kind: pubsub.googleapis.com/Topic selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name - kind: compute.googleapis.com/Disk selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name - kind: pubsub.googleapis.com/Subscription selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name - kind: iam.googleapis.com/ServiceAccount selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view the default mapping and blueprint definitions for each resource type. ProjectClusterCloud FunctionPub/Sub TopicCompute DiskPub/Sub SubscriptionService Account BlueprintJSON Create in Port ``` { "identifier": "gcpProject", "title": "GCP Project", "icon": "GoogleCloud", "schema": { "properties": {} }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {} } ``` Mapping configurationYAML ``` - kind: cloudresourcemanager.googleapis.com/Project selector: query: 'true' port: entity: mappings: identifier: .name blueprint: '"gcpProject"' title: .display_name properties: labels: .labels ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `preserveApiResponseCaseStyle` | `boolean` | Controls whether to preserve the Google Cloud API's original field format instead of using protobuf's default snake case. When False (default): Uses protobuf's default snake\_case format (existing behavior). When True: Preserves the specific API's original format (e.g., camelCase for PubSub). If not set, defaults to False to maintain existing behavior (snake\_case for all APIs).Note that this setting does not affect resources fetched from the cloud asset API | BlueprintJSON Create in Port ``` { "identifier": "gcpCloudResource", "title": "Cloud Resource", "icon": "GoogleCloud", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: container.googleapis.com/Cluster selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name ``` BlueprintJSON Create in Port ``` { "identifier": "gcpCloudResource", "title": "Cloud Resource", "icon": "GoogleCloud", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: cloudfunctions.googleapis.com/CloudFunction selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name ``` BlueprintJSON Create in Port ``` { "identifier": "gcpCloudResource", "title": "Cloud Resource", "icon": "GoogleCloud", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: pubsub.googleapis.com/Topic selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `preserveApiResponseCaseStyle` | `boolean` | Controls whether to preserve the Google Cloud API's original field format instead of using protobuf's default snake case. When False (default): Uses protobuf's default snake\_case format (existing behavior). When True: Preserves the specific API's original format (e.g., camelCase for PubSub). If not set, defaults to False to maintain existing behavior (snake\_case for all APIs).Note that this setting does not affect resources fetched from the cloud asset API | BlueprintJSON Create in Port ``` { "identifier": "gcpCloudResource", "title": "Cloud Resource", "icon": "GoogleCloud", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: compute.googleapis.com/Disk selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name ``` BlueprintJSON Create in Port ``` { "identifier": "gcpCloudResource", "title": "Cloud Resource", "icon": "GoogleCloud", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: pubsub.googleapis.com/Subscription selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `preserveApiResponseCaseStyle` | `boolean` | Controls whether to preserve the Google Cloud API's original field format instead of using protobuf's default snake case. When False (default): Uses protobuf's default snake\_case format (existing behavior). When True: Preserves the specific API's original format (e.g., camelCase for PubSub). If not set, defaults to False to maintain existing behavior (snake\_case for all APIs).Note that this setting does not affect resources fetched from the cloud asset API | BlueprintJSON Create in Port ``` { "identifier": "gcpCloudResource", "title": "Cloud Resource", "icon": "GoogleCloud", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: iam.googleapis.com/ServiceAccount selector: query: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last blueprint: '"gcpCloudResource"' properties: type: .name | split("/") | .[-2] location: .location labels: .labels relations: project: .__project.name ``` ## Further examples[​](#further-examples "Direct link to Further examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/examples/.md) page for practical configurations and their corresponding blueprint definitions. --- # Cloud Function Sync protocol The GCP integration supports syncing resources from any HTTP endpoint implementing the Fivetran Cloud Function Sync protocol. With this approach, you can ingest data from external systems (such as employee records, DWH data, custom metrics, etc.) served by a Cloud Run service. ## Overview[​](#overview "Direct link to Overview") Cloud Function Sync lets you extend the GCP integration to pull data from HTTP endpoints that implement a simple request-response protocol. This is useful when your data lives outside GCP or in systems not supported by the Cloud Asset Inventory API. ### Use cases[​](#use-cases "Direct link to Use cases") * Sync employee/HR data from internal services. * Ingest DWH records (customers, transactions, products). * Fetch custom metrics from metrics services. * Sync data from SaaS platforms via Cloud Run proxy. ## Configuration[​](#configuration "Direct link to Configuration") ### Example blueprint[​](#example-blueprint "Direct link to Example blueprint") Below is an example blueprint definition for an employee resource type. You can adapt this structure to suit your own use case: Create in Port Example employee blueprint ``` { "identifier": "ACMEEmployee", "description": "ACME Employee", "title": "ACME Employee", "icon": "Hibob", "schema": { "properties": { "employeeId": { "title": "Employee ID", "type": "number" }, "tribe": { "title": "Tribe", "type": "string" }, "tribeOneLiner": { "title": "Tribe Description", "type": "string" }, "squad": { "title": "Squad", "type": "string" }, "pod": { "title": "Pod", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` ### Example mapping configuration[​](#example-mapping-configuration "Direct link to Example mapping configuration") Here is a sample mapping you can add to your GCP integration configuration. Update the details as needed for your connector and blueprint: Sample GCP integration resource mapping ``` resources: - kind: gcpCloudFunction selector: query: 'true' functionUrl: http://localhost:9000 resource: employees port: entity: mappings: identifier: .email blueprint: '"ACMEEmployee"' title: .email properties: employeeId: .employee_id tribe: .tribe tribeOneLiner: .tribe_one_liner squad: .squad pod: .pod createMissingRelatedEntities: false deleteDependentEntities: true ``` Example templates The blueprint and mapping shown above are examples to help you get started. You should replace the property names, identifiers, and URLs with values matching your own data structures and system. --- # Examples This page contains the base examples for mapping GCP resources to Port. This base example thrives to provide a simpler and more abstract way to map GCP resources to Port. The simplification is achieved by using the generic `cloudResource` blueprint, which can be used to map any GCP resource to Port. ## Mapping GCP Projects[​](#mapping-gcp-projects "Direct link to Mapping GCP Projects") The following example demonstrates how to ingest your GCP Projects to Port.
You can use the following Port blueprint definitions and integration configuration: **Project Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpProject", "title": "GCP Project", "icon": "GoogleCloud", "schema": { "properties": {} }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {} } ``` **Mapping configuration for Google Cloud Projects (click to expand)** ``` resources: - kind: cloudresourcemanager.googleapis.com/Project selector: query: "true" port: entity: mappings: identifier: ".name" blueprint: '"gcpProject"' title: ".display_name" ``` Here are the API references we used to create those blueprints and app config: * [Project](https://docs.cloud.google.com/resource-manager/reference/rest/v3/projects) ## Mapping Cloud Resources[​](#mapping-cloud-resources "Direct link to Mapping Cloud Resources") The following example demonstrates how to ingest your GCP Resources to Port.
You can use the following Port blueprint definitions and integration configuration: Relate resources and projects The resources below have a relation to the Project, so the creation of the [Project](#mapping-gcp-projects) is required. **Cloud Resource Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpCloudResource", "title": "Cloud Resource", "icon": "GoogleCloud", "schema": { "properties": { "type": { "icon": "Service", "title": "Type", "type": "string" }, "location": { "icon": "Home", "title": "Location", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` **Mapping configuration for cloud resources (click to expand)** ``` resources: - kind: container.googleapis.com/Cluster selector: query: "true" port: entity: mappings: identifier: ".name" title: '.name | split("/") | last' blueprint: '"gcpCloudResource"' properties: type: '.name | split("/") | .[-2]' location: ".location" relations: project: ".__project.name" - kind: cloudfunctions.googleapis.com/Function selector: query: "true" port: entity: mappings: identifier: ".name" title: '.name | split("/") | last' blueprint: '"gcpCloudResource"' properties: type: '.name | split("/") | .[-2]' location: ".location" relations: project: ".__project.name" - kind: pubsub.googleapis.com/Topic selector: query: "true" port: entity: mappings: identifier: ".name" title: ".name | split("/") | last" blueprint: '"gcpCloudResource"' properties: type: '.name | split("/") | .[-2]' location: ".location" relations: project: ".__project.name" ``` ## Mapping Extra Resources[​](#mapping-extra-resources "Direct link to Mapping Extra Resources") The resources in this page are only a few of the resources that the GCP Integration supports. If the resources you want to ingest into Port do not appear in these examples, you can head to the [Mapping Extra Resources](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/examples/mapping_extra_resources/.md) page to learn about all of the kinds of GCP resources that are supported by the GCP integration and how to map them into Port. --- # Map Extra Resources As you've probably looked at the [Examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/examples/.md) page, you've noticed that the GCP Integration supports some GCP resources, but most of them are documented in the Examples page. This page will help you understand what kind of GCP resources are supported by the GCP integration and how to map them into Port. ## Is the resource supported by the GCP Integration?[​](#is-the-resource-supported-by-the-gcp-integration "Direct link to Is the resource supported by the GCP Integration?") The GCP Integration is relying on GCP's Asset Inventory API. That means: * Does the type of resource I want to injest listed [here](https://cloud.google.com/asset-inventory/docs/supported-asset-types)? * If Yes, It's supported! * If not, please contact us, or [add the support to the integration yourself](https://github.com/port-labs/ocean/tree/main/integrations/gcp) ## Inspect the raw API response[​](#inspect-the-raw-api-response "Direct link to Inspect the raw API response") To write accurate mappings, you need to know which fields are available on a given resource. Use the `gcloud` CLI to fetch a sample resource from the [Asset Inventory API](https://cloud.google.com/asset-inventory/docs/overview): ``` gcloud asset list \ --asset-types="compute.googleapis.com/Instance" \ --project=YOUR_PROJECT_ID \ --format=json \ | jq '.[0].resource.data' ``` Replace the `--asset-types` value with the resource type you want to inspect (e.g. `run.googleapis.com/Service`, `storage.googleapis.com/Bucket`). The output shows the exact JSON structure that is available for your JQ mapping expressions. Finding the right asset type The asset type string matches the `kind` field you use in the integration configuration. You can find the full list in the [Cloud Asset supported resource types](https://cloud.google.com/asset-inventory/docs/supported-asset-types). In addition to the resource's own fields, the GCP integration enriches each resource with a `__project` object containing metadata about the GCP project the resource belongs to. This object includes fields like `name`, `project_id`, `display_name`, `parent`, `state`, `labels`, and `create_time`. You can reference these in your mappings using the `.__project` prefix (e.g. `.__project.name`, `.__project.labels.owner`). ## Map the resource to Port[​](#map-the-resource-to-port "Direct link to Map the resource to Port") After you've found the resource in the [Cloud Asset Supported resources](https://cloud.google.com/asset-inventory/docs/supported-asset-types), you can map it to Port by following these steps: ### Create a blueprint[​](#create-a-blueprint "Direct link to Create a blueprint") Create a Port blueprint definition for the resource. The blueprint definition is based on the resource API specified per asset type. A few examples: #### Compute resources[​](#compute-resources "Direct link to Compute resources") **Subnetwork Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpSubnetwork", "description": "This blueprint represents a GCP subnetwork in our software catalog", "title": "Subnetwork", "icon": "GoogleCloud", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "privateIpGoogleAccess": { "title": "Private IP Google Access", "type": "boolean" }, "internalIpv6Prefix": { "title": "Internal Ipv6 Prefix", "type": "string" }, "externalIpv6Prefix": { "title": "External Ipv6 Prefix", "type": "string" }, "ipCidrRange": { "title": "IP CIDR Range", "type": "string" }, "description": { "title": "Description", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` **Firewall Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpFirewall", "description": "This blueprint represents a GCP Firewall in our software catalog", "title": "Firewall", "icon": "GoogleCloud", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "network": { "title": "Network", "type": "string" }, "destinationRanges": { "title": "Destination Ranges", "type": "array" }, "sourceRanges": { "title": "Source Ranges", "type": "array" }, "priority": { "title": "Priority", "type": "number" }, "allowed": { "title": "Allowed", "type": "array" }, "denied": { "title": "Denied", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` **Subnetwork Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpSubnetwork", "description": "This blueprint represents a GCP subnetwork in our software catalog", "title": "Subnetwork", "icon": "GoogleCloud", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "privateIpGoogleAccess": { "title": "Private IP Google Access", "type": "boolean" }, "internalIpv6Prefix": { "title": "Internal Ipv6 Prefix", "type": "string" }, "externalIpv6Prefix": { "title": "External Ipv6 Prefix", "type": "string" }, "ipCidrRange": { "title": "IP CIDR Range", "type": "string" }, "description": { "title": "Description", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "project": { "target": "gcpProject", "title": "Project", "required": false, "many": false } } } ``` **Compute Instance Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpComputeInstance", "description": "This blueprint represents a GCP Compute Instance in our software catalog", "title": "Compute Instance", "icon": "GoogleCloud", "schema": { "properties": { "subnetworks": { "title": "Subnetwork", "type": "array" }, "cpuPlatform": { "title": "CPU Platform", "type": "string" }, "status": { "type": "string", "title": "Status", "enum": [ "RUNNING", "STOPPING", "TERMINATED" ], "enumColors": { "RUNNING": "green", "STOPPING": "lightGray", "TERMINATED": "red" } }, "createdAt": { "type": "string", "title": "Created At", "format": "date-time" }, "tags": { "type": "object", "title": "Tags" }, "deletionProtection": { "type": "boolean", "title": "Deletion Protection" }, "lastStartedAt": { "type": "string", "title": "Last Started At", "format": "date-time" }, "selfLink": { "type": "string", "title": "Self Link", "format": "url" }, "zone": { "type": "string", "title": "Zone" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "gcpProject", "required": false, "many": false } } } ``` #### Manage Data resources[​](#manage-data-resources "Direct link to Manage Data resources") **BigQuery Dataset Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpBigQueryDataset", "description": "This blueprint represents a GCP BigQuery dataset in our software catalog", "title": "BigQuery Dataset", "icon": "GoogleCloud", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "lastModified": { "type": "string", "title": "Last Modified", "format": "date-time" }, "createdTime": { "type": "string", "title": "Create Time", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "gcpProject", "required": false, "many": false } } } ``` **BigQuery Table Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpBigQueryTable", "description": "This blueprint represents a GCP BigQuery table in our software catalog", "title": "BigQuery Table", "icon": "GoogleCloud", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "createdTime": { "type": "string", "title": "Created Time", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "gcpProject", "required": false, "many": false }, "dataset": { "title": "Dataset", "target": "gcpBigQueryDataset", "required": false, "many": false } } } ``` **BigQuery Model Blueprint (click to expand)** Create in Port ``` { "identifier": "gcpBigQueryModel", "description": "This blueprint represents a GCP BigQuery model in our software catalog", "title": "BigQuery Model", "icon": "GoogleCloud", "schema": { "properties": { "location": { "title": "Location", "type": "string" }, "createdTime": { "type": "string", "title": "Created Time", "format": "date-time" }, "modelType": { "type": "string", "title": "Model Type" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "dataset": { "title": "Dataset", "target": "gcpBigQueryDataset", "required": false, "many": false }, "project": { "title": "Project", "target": "gcpProject", "required": false, "many": false } } } ``` #### Create integration configuration[​](#create-integration-configuration "Direct link to Create integration configuration") Create an integration configuration for the resource. The integration configuration is a YAML file that describes the ETL process to load data into the developer portal. **Mapping Configuration for Autoscaler, Firewall, Subnetwork, Compute Instance (click to expand)** ``` resources: - kind: compute.googleapis.com/Autoscaler selector: query: "true" port: entity: mappings: identifier: ".id" title: ".name" blueprint: '"gcpAutoScaler"' properties: location: .location description: ".description" minNumReplicas: ".autoscalingPolicy.minNumReplicas" maxNumReplicas: ".autoscalingPolicy.maxNumReplicas" recommendedSize: ".recommendedSize" target: ".target" relations: project: ".__project.name" - kind: compute.googleapis.com/Firewall selector: query: "true" port: entity: mappings: identifier: ".id" title: ".name" blueprint: '"gcpFirewall"' properties: location: .location network: ".network" destinationRanges: ".destinationRanges" sourceRanges: ".sourceRanges" priority: ".priority" allowed: ".allowed" denied: ".denied" relations: project: ".__project.name" - kind: compute.googleapis.com/Subnetwork selector: query: "true" port: entity: mappings: identifier: ".id" title: ".name" blueprint: '"gcpSubnetwork"' properties: location: .location privateIpGoogleAccess: ".privateIpGoogleAccess" internalIpv6Prefix: ".internalIpv6Prefix" externalIpv6Prefix: ".externalIpv6Prefix" ipCidrRange: ".ipCidrRange" description: ".description" relations: project: ".__project.name" - kind: compute.googleapis.com/Instance selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"gcpComputeInstance"' properties: machineType: .machineType createdAt: .creationTimestamp status: .status subnetworks: .networkInterfaces[].subnetwork cpuPlatform: .cpuPlatform selfLink: .selfLink tags: .tags deletionProtection: .deletionProtection lastStartedAt: .lastStartTimestamp zone: .zone | split("/")[-1] relations: project: .__project.name ``` **Mapping Configuration for BigQuery Dataset, Table and Model (click to expand)** ``` resources: - kind: bigquery.googleapis.com/Dataset selector: query: 'true' port: entity: mappings: identifier: .datasetReference.datasetId title: .datasetReference.datasetId blueprint: '"gcpBigQueryDataset"' properties: location: .location last_modified: >- if .lastModifiedTime != null then (.lastModifiedTime | tonumber / 1000 | todate) else null end createdTime: >- if .creationTime != null then (.creationTime | tonumber / 1000 | todate) else null end relations: project: .__project.name - kind: bigquery.googleapis.com/Table selector: query: 'true' port: entity: mappings: identifier: .id title: .tableReference.tableId blueprint: '"gcpBigQueryTable"' properties: location: .location createdTime: >- if .creationTime != null then (.creationTime | tonumber / 1000 | todate) else null end relations: project: .__project.name dataset: .tableReference.datasetId - kind: bigquery.googleapis.com/Model selector: query: 'true' port: entity: mappings: identifier: .modelReference.modelId title: .modelReference.modelId blueprint: '"gcpBigQueryModel"' properties: location: .location createdTime: >- if .creationTime != null then (.creationTime | tonumber / 1000 | todate) else null end modelType: .modelType relations: project: .__project.name dataset: .modelReference.datasetId ``` #### The integration configuration structure[​](#the-integration-configuration-structure "Direct link to The integration configuration structure") * The `kind` field describes the GCP resource type to be ingested into Port. The `kind` field should be set to the GCP resource type as it appears in the [supported resources guide](https://cloud.google.com/asset-inventory/docs/supported-asset-types). e.g. The resource type for the `Compute Instance` is `compute.googleapis.com/Instance` ``` resources: - kind: compute.googleapis.com/Instance selector: ... ``` * The `selector` field describes the GCP resource selection criteria. ``` resources: - kind: compute.googleapis.com/Instance selector: query: "true" # JQ boolean expression. If evaluated to false - this object will be skipped. port: ``` * The `query` field is a [JQ boolean query](https://stedolan.github.io/jq/manual/#Basicfilters), if evaluated to `false` - the resource will be skipped. Example use case - skip syncing resources that are not in a specific region. ``` query: .location == "global" ``` * The `port` field describes the Port entity to be created from the GCP resource. ``` resources: - kind: compute.googleapis.com/Instance selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: # Mappings between one GCP object to a Port entity. Each value is a JQ query. identifier: ".id" title: ".name" blueprint: '"gcpComputeInstance"' properties: location: .location machineType: ".machineType" subnetworks: ".networkInterfaces[].subnetwork" cpuPlatform: ".cpuPlatform" selfLink: ".selfLink" relations: project: ".__project.name" ``` * The `entity` field describes the Port entity to be created from the GCP resource. * The `mappings` field describes the mapping between the GCP resource and the Port entity. * The `identifier` field describes the GCP resource identifier. This field is required for all resources. ``` mappings: identifier: ".id" ``` * The `title` field describes the GCP resource title. This field is required for all resources. ``` mappings: title: ".name" ``` * The `blueprint` field describes the Port blueprint to be used to create the Port entity. This field is required for all resources. ``` mappings: blueprint: '"gcpComputeInstance"' ``` * The `properties` field describes the GCP resource properties to be mapped to the Port ``` mappings: identifier: ".id" title: ".name" blueprint: '"gcpComputeInstance"' properties: location: .location machineType: ".machineType" ``` ### Case Style Preservation[​](#case-style-preservation "Direct link to Case Style Preservation") For the resources listed below, this integration provides flexibility to either transform the properties to `snake_case` or preserve the original case style (e.g., `camelCase`) returned by the API for consistency with other resources. This applies to the following kinds: * Projects (`cloudresourcemanager.googleapis.com/Project`) * Organizations (`cloudresourcemanager.googleapis.com/Organization`) * Folders (`cloudresourcemanager.googleapis.com/Folder`) * Topics (`pubsub.googleapis.com/Topic`) * Subscriptions (`pubsub.googleapis.com/Subscription`) This feature is particularly useful for ensuring compatibility with downstream systems or processes that require `camelCase` formatting. #### How to Enable Case Style Conversion[​](#how-to-enable-case-style-conversion "Direct link to How to Enable Case Style Conversion") To convert the case style to camelCase, set `preserveApiResponseCaseStyle: true` in the selector configuration for the relevant API. For example: ``` - kind: pubsub.googleapis.com/Subscription selector: query: 'true' preserveApiResponseCaseStyle: 'true' port: entity: mappings: identifier: .name - kind: pubsub.googleapis.com/Topic selector: query: 'true' preserveApiResponseCaseStyle: 'true' port: entity: mappings: identifier: .name title: .name | split("/") | last ``` When `preserveApiResponseCaseStyle` is not set or set to `false` (default behavior), all property names will converted to snake\_case format regardless of their original case style in the API response. --- # Code quality & security Port's code quality & security integrations make it easy to keep your software catalog up to date based on information stored in your preferred code quality & security tool. For more information, check out the documentation for your respective tool: ## [📄️Aikido](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/aikido/.md) [Sync Aikido security findings and vulnerabilities into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/aikido/.md) ## [🗃Checkmarx](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/.md) ## [📄️Mend.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/mend/.md) [Sync Mend.io projects and SCA security findings into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/mend/.md) ## [📄️Snyk](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/snyk/.md) [Sync Snyk vulnerabilities, projects, and security findings into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/snyk/.md) ## [🗃SonarQube](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/.md) ## [🗃Wiz](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/.md) ## [📄️ArmorCode](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/armorcode/.md) [Sync ArmorCode security findings and ASPM data into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/armorcode/.md) ## [📄️BrowserStack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/browserstack/.md) [Learn how to ingest BrowserStack Automate test data including plans, projects, and builds into Port using the no-code Ocean custom integration.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/browserstack/.md) --- # Aikido ![Aikido icon](/img/guides/icons/Aikido.svg)![Aikido icon](/img/guides/icons/Aikido.svg) Loading version... Port's Aikido integration allows you to model Aikido resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Repositories](https://apidocs.aikido.dev/reference/listcoderepos)[Issues](https://apidocs.aikido.dev/reference/exportissues)[Issue Groups](https://apidocs.aikido.dev/reference/listopenissuegroups)[Containers](https://apidocs.aikido.dev/reference/listcontainerrepos)[Teams](https://apidocs.aikido.dev/reference/listteams) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Webhook Configuration[​](#webhook-configuration "Direct link to Webhook Configuration") To enable real-time data synchronization from Aikido to Port, you must configure webhooks in Aikido following [this guide](https://apidocs.aikido.dev/reference/webhooks). This setup allows Port to receive immediate notifications whenever relevant changes occur in Aikido. When setting up the webhook, the URL should follow the format: `/integration/webhook` IMPORTANT For security and event authenticity, we strongly recommend setting an HMAC secret in the Aikido dashboard. Once configured, make sure to set the corresponding value in your Port environment using the variable `OCEAN__INTEGRATION__CONFIG__WEBHOOK_SECRET` . This ensures Port can securely verify incoming webhook events from Aikido. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: repositories selector: query: 'true' port: entity: mappings: blueprint: '"aikidoRepository"' identifier: .id | tostring title: .name properties: name: .name provider: .provider externalRepoId: .external_repo_id active: .active url: .url branch: .branch lastScannedAt: .last_scanned_at - kind: issues selector: query: 'true' port: entity: mappings: blueprint: '"aikidoIssue"' identifier: .id | tostring title: (.rule // .id) | tostring properties: status: .status severity: .severity severityScore: .severity_score affectedFile: .affected_file attackSurface: .attack_surface type: .type rule: .rule codeRepoId: .code_repo_id codeRepoName: .code_repo_name relations: aikidoRepository: .code_repo_id | tostring ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") RepositoriesIssuesIssue GroupsContainersTeams BlueprintJSON Create in Port ``` { "identifier": "aikidoRepository", "title": "Aikido Repository", "icon": "Aikido", "schema": { "properties": { "name": { "type": "string", "title": "Repository Name" }, "provider": { "type": "string", "title": "Provider", "enum": ["github", "gitlab", "gitlab-server", "bitbucket", "azure_devops", "selfscan"] }, "externalRepoId": { "type": "string", "title": "External Repository ID" }, "active": { "type": "boolean", "title": "Active" }, "url": { "type": "string", "title": "Repository URL" }, "branch": { "type": "string", "title": "Default Branch" }, "lastScannedAt": { "type": "number", "title": "Last Scanned At" } }, "required": ["name", "provider"] }, "relations": { "aikidoIssue": { "title": "Issues", "target": "aikidoIssue", "required": false, "many": true } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: repositories selector: query: 'true' port: entity: mappings: blueprint: '"aikidoRepository"' identifier: .id | tostring title: .name properties: name: .name provider: .provider externalRepoId: .external_repo_id active: .active url: .url branch: .branch lastScannedAt: .last_scanned_at ``` Sample payloadJSON ``` { "id": 1, "name": "Compression service", "provider": "github", "external_repo_id": "R_kgDOI5RlKA", "active": true, "url": "https://api.github.com/repos/aikidemo/compression-service", "branch": "main", "last_scanned_at": 1720083163 } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `includeInactive` | `boolean` | Whether to include inactive repositories Default: `false`. | BlueprintJSON Create in Port ``` { "identifier": "aikidoIssue", "title": "Aikido Issue", "icon": "Aikido", "schema": { "properties": { "groupId": { "type": "number", "title": "Group ID" }, "attackSurface": { "type": "string", "title": "Attack Surface", "enum": ["backend", "frontend", "infrastructure", "docker_container", "cloud"] }, "status": { "type": "string", "title": "Status", "enum": ["open", "closed", "ignored", "snoozed"], "enumColors": { "open": "red", "closed": "green", "ignored": "yellow", "snoozed": "blue" } }, "severity": { "type": "string", "title": "Severity", "enum": ["critical", "high", "medium", "low"] }, "severityScore": { "type": "number", "title": "Severity Score" }, "type": { "type": "string", "title": "Issue Type", "enum": ["open_source", "leaked_secret", "cloud", "iac", "sast", "mobile", "surface_monitoring", "malware", "eol", "scm_security", "ai_pentest", "license"] }, "rule": { "type": "string", "title": "Rule Name" }, "affectedFile": { "type": "string", "title": "Affected File" }, "codeRepoName": { "type": "string", "title": "Code Repository Name" }, "codeRepoId": { "type": "number", "title": "Code Repository ID" }, "closedAt": { "type": "number", "title": "Closed At" } }, "required": ["status", "severity", "type"] }, "relations": { "aikidoRepository": { "title": "Repository", "target": "aikidoRepository", "required": false, "many": false } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: issues selector: query: 'true' port: entity: mappings: blueprint: '"aikidoIssue"' identifier: .id | tostring title: (.rule // .id) | tostring properties: status: .status severity: .severity severityScore: .severity_score affectedFile: .affected_file attackSurface: .attack_surface type: .type rule: .rule codeRepoId: .code_repo_id codeRepoName: .code_repo_name relations: aikidoRepository: .code_repo_id | tostring ``` Sample payloadJSON ``` { "id": 1, "group_id": 1, "attack_surface": "backend", "status": "open", "severity": 90, "severity_score": "critical", "type": "open_source", "rule": "SQL injection", "rule_id": "aik_cloud_aws_001", "affected_package": "minimist", "affected_file": "index.php", "first_detected_at": 1700489005, "code_repo_name": "test-service", "code_repo_id": 1, "container_repo_id": 1, "container_repo_name": "aikido/test-service", "sla_days": 5, "sla_remediate_by": 1700924603, "ignored_at": null, "ignored_by": "user", "closed_at": null, "start_line": 68, "end_line": 70, "snooze_until": null, "cwe_classes": [ "CWE-89" ], "installed_version": "4.2.0", "patched_versions": [ "4.2.1", "5.0.0" ], "license": null, "programming_language": "PHP" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `filterStatus` | `string` | Filter issues by status. Default: `"all"`. | | `filterSeverities` | `array` | Filter issues by one or more severities. Multiple values are combined with OR. | | `filterIssueType` | `string` | Filter issues by type. | BlueprintJSON Create in Port ``` { "identifier": "aikidoIssueGroup", "title": "Aikido Issue Group", "icon": "Aikido", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "type": { "type": "string", "title": "Issue Type", "enum": ["open_source", "leaked_secret", "cloud", "iac", "sast", "surface_monitoring", "malware", "eol", "scm_security", "ai_pentest", "license"] }, "severity": { "type": "string", "title": "Severity", "enum": ["critical", "high", "medium", "low"] }, "severityScore": { "type": "number", "title": "Severity Score" }, "groupStatus": { "type": "string", "title": "Group Status", "enum": ["new", "todo", "task_open", "task_closed", "pull_request_open"] }, "timeToFixMinutes": { "type": "number", "title": "Time To Fix (minutes)" }, "locations": { "type": "array", "title": "Locations" }, "howToFix": { "type": "string", "title": "How To Fix" }, "relatedCveIds": { "type": "array", "title": "Related CVE IDs", "items": { "type": "string" } } }, "required": ["severity", "type", "groupStatus", "severityScore"] }, "relations": { "aikidoRepositories": { "title": "Repositories", "target": "aikidoRepository", "required": false, "many": true }, "aikidoContainers": { "title": "Containers", "target": "aikidoContainer", "required": false, "many": true }, "aikidoTeam": { "title": "Team", "target": "aikidoTeam", "required": false, "many": false } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: issue_groups selector: query: 'true' port: entity: mappings: blueprint: '"aikidoIssueGroup"' identifier: .id | tostring title: .title properties: description: .description type: .type severityScore: .severity_score severity: .severity groupStatus: .group_status timeToFixMinutes: .time_to_fix_minutes locations: .locations howToFix: .how_to_fix relatedCveIds: .related_cve_ids relations: aikidoRepositories: >- [.locations[]? | select(.type == "code_repository") | (.id | tostring)] aikidoContainers: >- [.locations[]? | select(.type == "container_repository") | (.id | tostring)] # Populated only when scopeToTeam is true (uses the __team_id enrichment). aikidoTeam: .__team_id | tostring ``` Sample payloadJSON ``` { "id": 26361953, "type": "open_source", "title": "golang.org/x/crypto", "description": "Attacker can trigger DOS-attack", "severity_score": 92, "severity": "critical", "group_status": "new", "time_to_fix_minutes": 30, "locations": [ { "id": 1841546, "name": "test-azure-sync-project", "type": "code_repository" } ], "how_to_fix": "In order to fix all of these vulnerabilities, update golang.org/x/crypto to 0.45.0. In order to solve only the critical issues, update to 0.31.0 or upgrade one at a time.", "related_cve_ids": [ "CVE-2024-45337", "CVE-2025-22869", "CVE-2025-58181", "CVE-2025-47914" ], "__team_id": 1310048, "__team_name": "Backend Team" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `scopeToTeam` | `boolean` | Whether to fetch issue groups scoped per active team. When true, each issue group is enriched with \\_\_team\_id\ and \\_\_team\_name\ fields, which can be used to populate the \aikidoTeam\ relation on the \aikidoIssueGroup\ blueprint. Default: `false`. | BlueprintJSON Create in Port ``` { "identifier": "aikidoContainer", "title": "Aikido Container", "icon": "Aikido", "schema": { "properties": { "provider": { "type": "string", "title": "Provider" }, "cloudId": { "type": "number", "title": "Cloud ID" }, "registryId": { "type": "number", "title": "Registry ID" }, "registryName": { "type": "string", "title": "Registry Name" }, "tag": { "type": "string", "title": "Tag" }, "distro": { "type": "string", "title": "Distro" }, "distroVersion": { "type": "string", "title": "Distro Version" }, "lastScannedAt": { "type": "number", "title": "Last Scanned At" }, "lastScannedTag": { "type": "string", "title": "Last Scanned Tag" }, "lastPushedAt": { "type": "number", "title": "Last Pushed At" }, "createdAt": { "type": "number", "title": "Created At" }, "isActive": { "type": "boolean", "title": "Active" } }, "required": ["provider", "tag"] }, "relations": { "aikidoRepository": { "title": "Repository", "target": "aikidoRepository", "required": false, "many": false } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: containers selector: query: 'true' filterStatus: 'active' port: entity: mappings: blueprint: '"aikidoContainer"' identifier: .id | tostring title: .name properties: provider: .provider cloudId: .cloud_id registryId: .registry_id registryName: .registry_name tag: .tag distro: .distro distroVersion: .distro_version lastScannedAt: .last_scanned_at lastScannedTag: .last_scanned_tag lastPushedAt: .last_pushed_at createdAt: .created_at isActive: .is_active relations: aikidoRepository: .linked_code_repo_id | tostring ``` Sample payloadJSON ``` { "id": 1, "name": "pied-piper/compression", "provider": "aws", "cloud_id": null, "registry_id": 3, "registry_name": "Pied Piper Global Cloud Infrastructure", "tag": "prod-*", "distro": "debian", "distro_version": "12", "last_scanned_at": 1720083163, "last_scanned_tag": "prod-20250623", "linked_code_repo_id": 67, "last_pushed_at": 1720083163, "created_at": 1720070000, "is_active": true } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `filterStatus` | `string` | Filter containers by status: all, active, or inactive Default: `"active"`. | BlueprintJSON Create in Port ``` { "identifier": "aikidoTeam", "title": "Aikido Team", "icon": "Aikido", "schema": { "properties": { "externalSource": { "type": "string", "title": "External Source", "enum": ["github", "gitlab", "bitbucket"] }, "externalSourceId": { "type": "string", "title": "External Source ID" }, "responsibilities": { "type": "array", "title": "Responsibilities" }, "active": { "type": "boolean", "title": "Active" } }, "required": ["active"] }, "relations": { "aikidoRepositories": { "title": "Repositories", "target": "aikidoRepository", "required": false, "many": true }, "aikidoContainers": { "title": "Containers", "target": "aikidoContainer", "required": false, "many": true } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: team selector: query: 'true' port: entity: mappings: blueprint: '"aikidoTeam"' identifier: .id | tostring title: .name properties: externalSource: .external_source externalSourceId: .external_source_id responsibilities: .responsibilities active: .active relations: aikidoRepositories: >- .responsibilities | map(select(.type == "code_repository") | .id | tostring) aikidoContainers: >- .responsibilities | map(select(.type == "container_repository") | .id | tostring) ``` Sample payloadJSON ``` { "id": 1310045, "name": "test-azure-sync-project Team", "external_source": "azure_devops", "external_source_id": "f2aeaf29-ca65-4631-8554-a35ac5812617", "responsibilities": [ { "id": 1841542, "type": "code_repository", "included_paths": null, "excluded_paths": null }, { "id": 1841546, "type": "code_repository", "included_paths": null, "excluded_paths": null } ], "active": true } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Aikido 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 Aikido 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Aikido is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Aikido can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # ArmorCode ![ArmorCode icon](/img/guides/icons/ArmorCode.svg)![ArmorCode icon](/img/guides/icons/ArmorCode.svg) Loading version... Port's ArmorCode integration allows you to model ArmorCode resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Products](https://github.com/armor-code/acsdk#products)[Sub-Products](https://github.com/armor-code/acsdk#sub-products)[Findings](https://github.com/armor-code/acsdk#findings) ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: product selector: query: 'true' port: entity: mappings: blueprint: '"armorcodeProduct"' identifier: .id | tostring title: .name properties: name: .name description: .description businessOwner: .business_owner securityOwner: .security_owner - kind: sub-product selector: query: 'true' port: entity: mappings: blueprint: '"armorcodeSubProduct"' identifier: .id | tostring title: .name properties: name: .name repoLink: .repo_link programmingLanguage: .programming_language technologies: .technologies relations: product: .product_id - kind: finding selector: query: 'true' port: entity: mappings: blueprint: '"armorcodeFinding"' identifier: .id | tostring title: .title properties: source: .source description: .description mitigation: .mitigation severity: .severity findingCategory: .finding_category status: .status productStatus: .product_status subProductStatuses: .sub_product_statuses title: .title toolSeverity: .tool_severity createdAt: .created_at lastUpdated: .last_updated cwe: .cwe cve: .cve link: .link riskScore: .risk_score findingScore: .finding_score relations: product: .product_id subProduct: .sub_product_id ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") ProductsSub-ProductsFindings Default mappingYAML ``` - kind: product selector: query: 'true' port: entity: mappings: blueprint: '"armorcodeProduct"' identifier: .id | tostring title: .name properties: name: .name description: .description businessOwner: .business_owner securityOwner: .security_owner ``` Sample payloadJSON ``` { "id": 1, "name": "E-commerce Platform", "description": "Main e-commerce application for online retail", "business_owner": "John Smith", "security_owner": "Sarah Johnson", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T14:45:00Z" } ``` Default mappingYAML ``` - kind: sub-product selector: query: 'true' port: entity: mappings: blueprint: '"armorcodeSubProduct"' identifier: .id | tostring title: .name properties: name: .name repoLink: .repo_link programmingLanguage: .programming_language technologies: .technologies relations: product: .product_id ``` Sample payloadJSON ``` { "id": 101, "name": "payment-service", "repo_link": "https://github.com/company/payment-service", "programming_language": "Java", "technologies": ["Spring Boot", "PostgreSQL", "Redis"], "product_id": 1, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T14:45:00Z" } ``` Default mappingYAML ``` - kind: finding selector: query: 'true' port: entity: mappings: blueprint: '"armorcodeFinding"' identifier: .id | tostring title: .title properties: source: .source description: .description mitigation: .mitigation severity: .severity findingCategory: .finding_category status: .status productStatus: .product_status subProductStatuses: .sub_product_statuses title: .title toolSeverity: .tool_severity createdAt: .created_at lastUpdated: .last_updated cwe: .cwe cve: .cve link: .link riskScore: .risk_score findingScore: .finding_score relations: product: .product_id subProduct: .sub_product_id ``` Sample payloadJSON ``` { "id": 1001, "title": "SQL Injection Vulnerability", "source": "SAST", "description": "Potential SQL injection vulnerability detected in user input validation", "mitigation": "Use parameterized queries and input validation", "severity": "HIGH", "finding_category": "Code Security", "status": "OPEN", "product_status": "ACTIVE", "sub_product_statuses": "ACTIVE", "tool_severity": "HIGH", "created_at": "2024-01-15T10:30:00Z", "last_updated": "2024-01-20T14:45:00Z", "cwe": ["CWE-89"], "cve": ["CVE-2023-1234"], "link": "https://app.armorcode.com/findings/1001", "risk_score": 8.5, "finding_score": 7.2, "product_id": 1, "sub_product_id": 101 } ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # BrowserStack Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's BrowserStack integration allows you to model BrowserStack Automate test data in your software catalog and ingest it using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The BrowserStack integration can ingest the following resources into Port: * `plan` - Account utilization and capacity from [`GET /automate/plan.json`](https://www.browserstack.com/docs/automate/api-reference/selenium/plan). * `project` - Automate projects and team groupings from [`GET /automate/projects.json`](https://www.browserstack.com/docs/automate/api-reference/selenium/project). * `build` - Build history and pass/fail trends from builds nested in [`GET /automate/projects/{project_id}.json`](https://www.browserstack.com/docs/automate/api-reference/selenium/project). ## Entity hierarchy[​](#entity-hierarchy "Direct link to Entity hierarchy") The integration models this relationship in Port: ``` Project → Build ``` The plan resource is account-level utilization data and is not part of this hierarchy. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, you need: * A BrowserStack account with access to [BrowserStack Automate](https://www.browserstack.com/automate). * Your BrowserStack **username** and **access key** for API authentication. **To find your BrowserStack credentials:** 1. Log in to [BrowserStack](https://www.browserstack.com/). 2. Go to [Account settings](https://www.browserstack.com/accounts/settings). 3. Copy your **username** and **access key**. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.getport.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. In the configuration form, enter the following values: * **Identifier**: `browserstack-ocean`. * **Base URL**: `https://api.browserstack.com`. * **Authentication type**: `basic`. * **Username**: your BrowserStack username. * **Password**: your BrowserStack access key. 3. Click **Save & install**. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation 1. Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, `YOUR_BROWSERSTACK_USERNAME`, and `YOUR_BROWSERSTACK_ACCESS_KEY`. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install browserstack-ocean port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set scheduledResyncInterval=60 \ --set integration.identifier="browserstack-ocean" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://api.browserstack.com" \ --set integration.config.authType="basic" \ --set integration.config.paginationType="none" \ --set integration.secrets.username="YOUR_BROWSERSTACK_USERNAME" \ --set integration.secrets.password="YOUR_BROWSERSTACK_ACCESS_KEY" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters This table summarizes the available parameters for the installation. | Parameter | Description | Example | Required | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | -------- | | `port.clientId` | Your Port [client ID](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) | | ✅ | | `port.clientSecret` | Your Port [client secret](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) | | ✅ | | `port.baseUrl` | Your Port API URL - `https://api.port.io` for EU, `https://api.us.port.io` for US | | ✅ | | `integration.config.baseUrl` | The base URL of the BrowserStack API. Endpoint paths in your mapping include the `/automate` prefix. | `https://api.browserstack.com` | ✅ | | `integration.config.authType` | The authentication type (use `basic` for BrowserStack) | `basic` | ✅ | | `integration.secrets.username` | Your BrowserStack username | | ✅ | | `integration.secrets.password` | Your BrowserStack access key | | ✅ | | `integration.config.paginationType` | Pagination method. Use `none` for the default mappings on this page. | `none` | ❌ | | `integration.eventListener.type` | The event listener type. Read more about [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) | `POLLING` | ✅ | | `integration.type` | The integration type (must be `custom` for the no-code Ocean custom integration). | `custom` | ✅ | | `integration.identifier` | Unique identifier for the integration instance | `browserstack-ocean` | ✅ | | `scheduledResyncInterval` | The number of minutes between each resync. Read more about [scheduledResyncInterval](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) | `60` | ❌ | | `initializePortResources` | When set to `true`, the integration will create default blueprints and the Port app config mapping | `true` | ❌ | | `sendRawDataExamples` | Enable sending raw data examples from the BrowserStack API to Port for testing and managing the integration mapping. Default is `true` | `true` | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, `YOUR_BROWSERSTACK_USERNAME`, and `YOUR_BROWSERSTACK_ACCESS_KEY`. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://api.browserstack.com" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="basic" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="none" \ -e OCEAN__INTEGRATION__SECRETS__USERNAME="YOUR_BROWSERSTACK_USERNAME" \ -e OCEAN__INTEGRATION__SECRETS__PASSWORD="YOUR_BROWSERSTACK_ACCESS_KEY" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before the integration can sync data, you need to create the required blueprints in Port. These blueprints define the data model for your BrowserStack resources. **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on `+ Blueprint`. 3. Click on the `{...} Edit JSON` button. 4. Copy and paste the following JSON configuration: Create blueprints in order We recommend creating blueprints in the order they appear below. Create the **Project** blueprint before the **Build** blueprint, since builds reference projects. **Plan blueprint (click to expand)** Account-level Automate utilization and capacity: Create in Port ``` { "identifier": "browserstackPlan", "title": "BrowserStack Plan", "description": "BrowserStack Automate plan utilization and parallel session capacity", "schema": { "properties": { "automatePlan": { "type": "string", "title": "Automate Plan" }, "parallelSessionsRunning": { "type": "number", "title": "Parallel Sessions Running" }, "parallelSessionsMaxAllowed": { "type": "number", "title": "Parallel Sessions Max Allowed" }, "teamParallelSessionsMaxAllowed": { "type": "number", "title": "Team Parallel Sessions Max Allowed" }, "queuedSessions": { "type": "number", "title": "Queued Sessions" }, "queuedSessionsMaxAllowed": { "type": "number", "title": "Queued Sessions Max Allowed" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Project blueprint (click to expand)** Automate projects used to group builds by team or product: Create in Port ``` { "identifier": "browserstackProject", "title": "BrowserStack Project", "icon": "Team", "description": "A BrowserStack Automate project", "schema": { "properties": { "groupId": { "type": "number", "title": "Group ID" }, "userId": { "type": "number", "title": "User ID" }, "subGroupId": { "type": "number", "title": "Sub Group ID" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Build blueprint (click to expand)** Test builds with pass/fail status and a relation to their parent project: Create in Port ``` { "identifier": "browserstackBuild", "title": "BrowserStack Build", "icon": "Pipeline", "description": "A BrowserStack Automate test build", "schema": { "properties": { "duration": { "type": "number", "title": "Duration (seconds)" }, "status": { "type": "string", "title": "Status", "enum": ["running", "done", "timeout", "failed"], "enumColors": { "running": "blue", "done": "green", "timeout": "orange", "failed": "red" } }, "buildTag": { "type": "string", "title": "Build Tag" }, "framework": { "type": "string", "title": "Framework" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "browserstackProject", "required": false, "many": false } } } ``` 5. Click **Save** after creating each blueprint. ## Configuration[​](#configuration "Direct link to Configuration") After installation, define which endpoints to sync in your integration configuration. Each resource maps an API endpoint to Port entities using [JQ expressions](https://stedolan.github.io/jq/manual/) to transform the data. **Key mapping components:** * **`kind`**: The API endpoint path, combined with your base URL to form the full request URL. * **`selector.query`**: JQ filter to include or exclude entities. Use `'true'` to sync all. * **`selector.data_path`**: JQ expression pointing to the array of items in the response. * **`port.entity.mappings`**: How to map API fields to Port entity properties and relations. For more details on how the no-code Ocean custom integration works, see the [How it works](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview#how-it-works) section. BrowserStack API reference The full BrowserStack Automate API reference is available in the [BrowserStack docs](https://www.browserstack.com/docs/automate/api-reference/selenium/automate-api). Each mapping section below links directly to the relevant endpoint documentation. **BrowserStack API response formats:** With base URL `https://api.browserstack.com`, each `kind` includes the `/automate` path prefix: | `kind` | Response shape | `data_path` | | -------------------------------------- | ------------------------------------------- | ------------------- | | `/automate/plan.json` | Single JSON object | `[.]` | | `/automate/projects.json` | Direct array of projects | *(omit)* | | `/automate/projects/{project_id}.json` | Project object with a nested `builds` array | `.project.builds[]` | **Example project detail response (click to expand)** ``` { "project": { "id": 17122553, "name": "Port Integration", "builds": [ { "name": "Build 1", "hashed_id": "0059939e770b361b836783145d438d05f3594cb1", "duration": 4, "status": "done", "automation_project_id": 17122553, "framework": "selenium", "created_at": "2026-06-09T17:54:05.000Z", "updated_at": "2026-06-09T17:54:11.000Z" } ] } } ``` Sync order matters We recommend adding mappings in the order shown below. Sync **projects** first, then **builds**, so build-to-project relations resolve correctly. Sync the **plan** resource at any time. **To configure the mappings:** 1. Go to your [connectors page](https://app.getport.io/settings/data-sources). 2. Find your BrowserStack integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below. **Plan mapping (click to expand)** **API reference:** [`GET /automate/plan.json`](https://www.browserstack.com/docs/automate/api-reference/selenium/plan) ``` resources: - kind: /automate/plan.json selector: query: 'true' data_path: '[.]' port: entity: mappings: identifier: '"browserstack-automate-plan"' title: .automate_plan blueprint: '"browserstackPlan"' properties: automatePlan: .automate_plan parallelSessionsRunning: .parallel_sessions_running parallelSessionsMaxAllowed: .parallel_sessions_max_allowed teamParallelSessionsMaxAllowed: .team_parallel_sessions_max_allowed queuedSessions: .queued_sessions queuedSessionsMaxAllowed: .queued_sessions_max_allowed ``` **Projects mapping (click to expand)** **API reference:** [`GET /automate/projects.json`](https://www.browserstack.com/docs/automate/api-reference/selenium/project) ``` resources: - kind: /automate/projects.json selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"browserstackProject"' properties: groupId: .group_id userId: .user_id subGroupId: .sub_group_id createdAt: .created_at updatedAt: .updated_at ``` **Builds mapping (click to expand)** **API reference:** [`GET /automate/projects/{project_id}.json`](https://www.browserstack.com/docs/automate/api-reference/selenium/project) This mapping uses [nested endpoints](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/configuration/.md#nested-endpoints) to fetch builds for each project. ``` resources: - kind: /automate/projects/{projectId}.json selector: query: 'true' data_path: .project.builds[] path_parameters: projectId: endpoint: /automate/projects.json field: .[].id filter: 'true' port: entity: mappings: identifier: .hashed_id title: .name blueprint: '"browserstackBuild"' properties: duration: .duration status: .status buildTag: .tags framework: .framework createdAt: .created_at updatedAt: .updated_at relations: project: .__projectId | tostring ``` 5. Click **Save** to apply the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to test different API endpoints or explore the BrowserStack API before committing to a configuration, use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md). **The interactive builder helps you:** 1. Test your BrowserStack API endpoints with live data. 2. Automatically detect the data structure and field types. 3. Generate blueprints and resource mappings tailored to your preferences. 4. Get installation commands with your configuration pre-filled. ## Additional resources[​](#additional-resources "Direct link to Additional resources") * [BrowserStack Automate API reference](https://www.browserstack.com/docs/automate/api-reference/selenium/automate-api) - full reference for all available endpoints and response schemas. * [BrowserStack plan API](https://www.browserstack.com/docs/automate/api-reference/selenium/plan) - details on utilization and capacity fields. * [BrowserStack project API](https://www.browserstack.com/docs/automate/api-reference/selenium/project) - details on project list and project detail endpoints. * [BrowserStack build API](https://www.browserstack.com/docs/automate/api-reference/selenium/build) - details on build list, status values, and pagination parameters. * [No-code Ocean custom integration overview](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) - learn how the no-code Ocean custom integration framework works. * [No-code Ocean custom integration configuration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/configuration/.md) - advanced mapping options, nested endpoints, and pagination configuration. --- # Checkmarx One ![Checkmarx icon](/img/guides/icons/Checkmarx.svg)![Checkmarx icon](/img/guides/icons/Checkmarx.svg) Loading version... Port's Checkmarx One integration allows you to model Checkmarx One resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/j4vd1fubv8m4z-retrieve-list-of-projects)[Scan](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/v2u57n2kkov6s-retrieve-list-of-scans)[SAST](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/whqbw17zn6rg1-retrieve-scan-results-all-scanners)[SCA](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/whqbw17zn6rg1-retrieve-scan-results-all-scanners)[KICS](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/whqbw17zn6rg1-retrieve-scan-results-all-scanners)[Container Security](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/whqbw17zn6rg1-retrieve-scan-results-all-scanners)[API Security](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/whqbw17zn6rg1-retrieve-scan-results-all-scanners)[Application](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/0b920aabeb464-retrieve-applications-info)[DAST Scan Environment](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/pzj8a9124pybu-dast-scans-service-rest-api)[DAST Scan](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/pzj8a9124pybu-dast-scans-service-rest-api)[DAST Scan Result](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/qk81mcfw8fxg5-dast-results-service-rest-api) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A Checkmarx One enterprise account. * A Port organization with admin permissions. Checkmarx One API Access The Checkmarx One API is available for enterprise customers. You need an API key to authenticate with the Checkmarx One API. ### Checkmarx One Credentials[​](#checkmarx-one-credentials "Direct link to Checkmarx One Credentials") You need the following connection details to configure Checkmarx One: * **Checkmarx One Base URL**: The API endpoint URL for your region. * **Checkmarx One IAM URL**: The IAM authentication URL for your region. * **Tenant Name**: Your Checkmarx One tenant name. * **API Key**: Your Checkmarx One API key. **Regional URLs (click to expand)** Checkmarx One provides different URLs based on your region: * **US**: `https://ast.checkmarx.net/api` / `https://iam.checkmarx.net` * **EU**: `https://eu.ast.checkmarx.net/api` / `https://eu.iam.checkmarx.net` * **US2**: `https://us.ast.checkmarx.net/api` / `https://us.iam.checkmarx.net` * **EU2**: `https://eu-2.ast.checkmarx.net/api` / `https://eu-2.iam.checkmarx.net` * **DEU**: `https://deu.ast.checkmarx.net/api` / `https://deu.iam.checkmarx.net` * **ANZ**: `https://anz.ast.checkmarx.net/api` / `https://anz.iam.checkmarx.net` * **IND**: `https://ind.ast.checkmarx.net/api` / `https://ind.iam.checkmarx.net` * **SNG**: `https://sng.ast.checkmarx.net/api` / `https://sng.iam.checkmarx.net` * **UAE**: `https://mea.ast.checkmarx.net/api` / `https://mea.iam.checkmarx.net` 1. **Finding Your Checkmarx One Base URL and IAM URL**: * Login to your Checkmarx One account. * Navigate to **Settings** > **Identity and Access Management**. * The base URL and IAM URL will be displayed based on your region. * Copy and save both URLs for use in the integration configuration. 2. **Getting Your Tenant Name**: * Your tenant name is typically part of your login URL. * It can also be found in your account settings. * Copy and save your tenant name. 3. **Generating an API Key**: * Login to Checkmarx One with admin permissions. * Navigate to **Settings** > **Identity and Access Management** > **API Keys**. * Click **Add API Key**. * Provide a descriptive name for the API key. * Select the appropriate permissions (read access to projects, scans, and results). * Click **Create API Key**. * Copy and securely store the generated API key. API Key Security Store your API key securely and never share it. The API key provides access to your Checkmarx One data. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to 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: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId - kind: sast selector: query: 'true' port: entity: mappings: identifier: .resultHash title: .queryName blueprint: '"checkmarxSast"' properties: firstScanId: .firstScanID status: .status state: .state severity: (.severity // empty) confidenceLevel: .confidenceLevel created: .firstFoundAt nodes: (.nodes // empty) cweId: (.cweID // empty) relations: scan: .scanID - kind: sca selector: query: 'true' port: entity: mappings: identifier: .id title: .data.packageIdentifier blueprint: '"checkmarxSCA"' properties: firstScanId: .firstScanId status: .status state: .state severity: .severity created: .created description: .description packageIdentifier: .data.packageIdentifier recommendations: .data.recommendations recommendedVersion: .data.recommendedVersion packageData: .data.packageData cweId: .vulnerabilityDetails.cweId relations: scan: .__scan_id - kind: kics selector: query: 'true' port: entity: mappings: identifier: .id title: .description blueprint: '"checkmarxKics"' properties: type: .type firstScanId: .firstScanId id: .id status: .status state: .state severity: (.severity // empty) confidenceLevel: .confidenceLevel created: .created description: .description fileName: (.data.fileName // empty) line: (.data.line // empty) platform: (.data.platform // empty) issueType: (.data.issueType // empty) expectedValue: (.data.expectedValue // empty) value: (.data.value // empty) relations: scan: .__scan_id - kind: containers selector: query: 'true' port: entity: mappings: identifier: .id title: .id blueprint: '"checkmarxContainerSecurity"' properties: firstScanId: .firstScanId status: .status state: .state severity: .severity confidenceLevel: .confidenceLevel created: .created description: .description packageName: .data.packageName packageVersion: .data.packageVersion imageName: .data.imageName imageTag: .data.imageTag imageFilePath: .data.imageFilePath cweId: .vulnerabilityDetails.cweId relations: scan: .__scan_id - kind: apisec selector: query: 'true' port: entity: mappings: identifier: .risk_id title: .name blueprint: '"checkmarxApiSec"' properties: riskId: .risk_id apiId: .api_id severity: (.severity // empty) name: .name status: .status httpMethod: .http_method url: .url origin: .origin documented: .documented authenticated: .authenticated discoveryDate: .discovery_date scanId: .scan_id sastRiskId: (.sast_risk_id // empty) projectId: .project_id state: .state relations: scan: .scan_id ``` **Deep links to Checkmarx One dashboard** If you need to ingest deep links back to the Checkmarx One dashboard, you can construct them using jq expressions in your mapping configuration. Combine your Checkmarx One UI base URL with relevant path parameters from the data returned by the API. For example, to create a dashboard link for a project: ``` dashboardUrl: '"https://your-region.ast.checkmarx.net/projects/" + .id' ``` Or for a specific scan result: ``` scanUrl: '"https://your-region.ast.checkmarx.net/projects/" + .projectId + "/scans/" + "?id=" + .id' ``` Replace `your-region` with your actual Checkmarx One region (e.g., `eu`, `us`, `deu`, etc.) and adjust the path structure based on your Checkmarx One dashboard URL format. ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view the mapping and blueprint definition for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/examples/.md). ProjectScanSASTSCAKICSContainer SecurityAPI SecurityApplicationDAST Scan EnvironmentDAST ScanDAST Scan Result BlueprintJSON Create in Port ``` { "identifier": "checkmarxProject", "title": "Checkmarx Project", "icon": "Checkmarx", "schema": { "properties": { "name": { "type": "string", "title": "Project Name" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" }, "tags": { "type": "object", "title": "Tags" }, "repoUrl": { "type": "string", "title": "Repository URL" }, "mainBranch": { "type": "string", "title": "Main Branch" }, "origin": { "type": "string", "title": "Origin" }, "criticality": { "type": "number", "title": "Criticality" } }, "required": ["name"] }, "relations": {} } ``` Mapping configurationYAML ``` - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxScan", "title": "Checkmarx Scan", "icon": "Checkmarx", "schema": { "properties": { "status": { "type": "string", "title": "Status", "enum": [ "Queued", "Running", "Completed", "Failed", "Partial", "Canceled" ], "description": "The status of the scan. Possible values: Queued, Running, Completed, Failed, Partial, Canceled." }, "branch": { "type": "string", "title": "Branch", "description": "The branch of the repository that was scanned." }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "The date and time when the scan was created." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At", "description": "The date and time when the scan was last updated." }, "projectId": { "type": "string", "title": "Project ID", "description": "The identifier of the project to which this scan belongs." }, "userAgent": { "type": "string", "title": "User Agent", "description": "The user agent used to initiate the scan." }, "configs": { "type": "object", "title": "Configurations", "description": "Configuration details for the scan." }, "statusDetails": { "type": "array", "items": { "type": "object" }, "title": "Status Details", "description": "Detailed status information for the scan." } }, "required": ["status", "projectId"] }, "relations": { "project": { "title": "Project", "target": "checkmarxProject", "many": false, "required": true } } } ``` Mapping configurationYAML ``` - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxSast", "description": "Represents a Checkmarx Static Application Security Testing (SAST) finding in the catalogue", "title": "Checkmarx SASTs", "icon": "Checkmarx", "schema": { "properties": { "firstScanId": { "type": "string", "title": "First Scan ID", "description": "The identifier of the first scan in which this SAST finding appeared." }, "status": { "type": "string", "title": "Status", "description": "The status of the SAST finding", "enum": [ "NEW", "RECURRENT", "FIXED" ], "enumColors": { "NEW": "lightGray", "RECURRENT": "lightGray", "FIXED": "lightGray" } }, "state": { "icon": "DefaultProperty", "type": "string", "title": "State", "description": "The state of the SAST finding" }, "severity": { "type": "string", "description": "The severity level of the SAST finding", "title": "Severity", "enum": [ "LOW", "MEDIUM", "HIGH", "CRITICAL" ], "enumColors": { "LOW": "lightGray", "MEDIUM": "lightGray", "HIGH": "lightGray", "CRITICAL": "lightGray" } }, "confidenceLevel": { "type": "number", "title": "Confidence Level", "description": "The confidence level (0-100) assigned to the SAST finding", "minimum": 0, "maximum": 100 }, "created": { "type": "string", "title": "Created At", "description": "The date and time when the SAST finding was created", "format": "date-time" }, "description": { "type": "string", "title": "Description", "description": "The description of the SAST result" }, "nodes": { "items": { "type": "object" }, "icon": "DefaultProperty", "type": "array", "title": "Nodes", "description": "The list of nodes or code locations related to the SAST finding" }, "cweId": { "type": "string", "title": "CWE ID", "description": "The CWE (Common Weakness Enumeration) identifier associated with this SAST finding" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: sast selector: query: 'true' port: entity: mappings: identifier: .resultHash title: .queryName blueprint: '"checkmarxSast"' properties: firstScanId: .firstScanID status: .status state: .state severity: (.severity // empty) confidenceLevel: .confidenceLevel created: .firstFoundAt nodes: (.nodes // empty) cweId: (.cweID // empty) relations: scan: .scanID ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxSCA", "title": "Checkmarx SCA", "icon": "Checkmarx", "schema": { "properties": { "severity": { "type": "string", "title": "Severity", "description": "The severity level of the SCA finding." }, "state": { "type": "string", "title": "State", "description": "The state of the SCA finding." }, "description": { "type": "string", "title": "Description", "description": "A description of the SCA finding." }, "cweId": { "type": "string", "title": "CWE ID", "description": "The Common Weakness Enumeration (CWE) identifier related to the vulnerability." }, "status": { "type": "string", "title": "Status", "description": "The current status of the SCA finding." }, "created": { "type": "string", "format": "date-time", "title": "Created", "description": "The date and time when the SCA finding was created." }, "firstScanId": { "type": "string", "title": "First Scan ID", "description": "The identifier of the first scan in which this SCA issue was detected." }, "packageIdentifier": { "type": "string", "title": "Package Identifier", "description": "The identifier of the affected package." }, "recommendations": { "type": "array", "items": { "type": "string" }, "title": "Recommendations", "description": "Recommended actions to remediate the SCA finding." }, "recommendedVersion": { "type": "string", "title": "Recommended Version", "description": "The recommended version of the package to resolve the vulnerability." }, "packageData": { "items": { "type": "object" }, "type": "array", "title": "Package Data" }, "confidenceLevel": { "type": "string", "title": "Confidence Level", "description": "The confidence level of the SCA finding." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: sca selector: query: 'true' port: entity: mappings: identifier: .id title: .data.packageIdentifier blueprint: '"checkmarxSCA"' properties: firstScanId: .firstScanId status: .status state: .state severity: .severity created: .created description: .description packageIdentifier: .data.packageIdentifier recommendations: .data.recommendations recommendedVersion: .data.recommendedVersion packageData: .data.packageData cweId: .vulnerabilityDetails.cweId relations: scan: .__scan_id ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxKics", "description": "Represents a KICS issue detected by Checkmarx One", "title": "Checkmarx KICS", "icon": "Checkmarx", "schema": { "properties": { "firstScanId": { "type": "string", "title": "First Scan ID", "description": "The identifier of the first scan in which this KICS issue was detected" }, "status": { "type": "string", "title": "Status", "description": "The current status of the KICS issue, indicating if it is new, recurring, or has been fixed.", "enum": [ "NEW", "RECURRENT", "FIXED" ], "enumColors": { "NEW": "lightGray", "RECURRENT": "lightGray", "FIXED": "lightGray" } }, "state": { "type": "string", "description": "The state of the KICS issue, reflecting its verification and remediation status", "title": "State" }, "severity": { "type": "string", "title": "Severity", "enum": [ "LOW", "MEDIUM", "HIGH", "CRITICAL", "INFO" ], "enumColors": { "LOW": "lightGray", "MEDIUM": "lightGray", "HIGH": "lightGray", "CRITICAL": "lightGray", "INFO": "lightGray" } }, "created": { "type": "string", "title": "Created At", "description": "The date and time when the KICS issue was first identified", "format": "date-time" }, "description": { "icon": "DefaultProperty", "type": "string", "title": "Description", "description": "A description of the KICS issue." }, "fileName": { "type": "string", "title": "File Name", "description": "The name of the file where the KICS issue was found." }, "line": { "type": "string", "title": "Line Number", "description": "The line number in the file where the KICS issue occurs" }, "platform": { "type": "string", "title": "Platform" }, "issueType": { "type": "string", "title": "Issue Type", "description": "The type or category of the KICS issue" }, "expectedValue": { "type": "string", "title": "Expected Value", "description": "The value that was expected in the configuration" }, "value": { "type": "string", "title": "Value", "description": "The actual value found in the configuration that triggered the KICS issue" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: kics selector: query: 'true' port: entity: mappings: identifier: .id title: .description blueprint: '"checkmarxKics"' properties: type: .type firstScanId: .firstScanId id: .id status: .status state: .state severity: (.severity // empty) confidenceLevel: .confidenceLevel created: .created description: .description fileName: (.data.fileName // empty) line: (.data.line // empty) platform: (.data.platform // empty) issueType: (.data.issueType // empty) expectedValue: (.data.expectedValue // empty) value: (.data.value // empty) relations: scan: .__scan_id ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxContainerSecurity", "title": "Checkmarx Container Security", "icon": "Checkmarx", "schema": { "properties": { "firstScanId": { "type": "string", "title": "First Scan ID", "description": "The identifier of the first scan in which this container security issue was detected." }, "status": { "type": "string", "title": "Status", "description": "The current status of the container security finding." }, "state": { "type": "string", "title": "State", "description": "The state of the container security finding." }, "severity": { "type": "string", "title": "Severity", "description": "The severity level of the container security finding." }, "confidenceLevel": { "type": "string", "title": "Confidence Level", "description": "The confidence level of the container security finding." }, "created": { "type": "string", "format": "date-time", "title": "Created", "description": "The date and time when the container security finding was created." }, "description": { "type": "string", "title": "Description", "description": "A description of the container security finding." }, "packageName": { "type": "string", "title": "Package Name", "description": "The name of the affected package in the container." }, "packageVersion": { "type": "string", "title": "Package Version", "description": "The version of the affected package in the container." }, "imageName": { "type": "string", "title": "Image Name", "description": "The name of the container image." }, "imageTag": { "type": "string", "title": "Image Tag", "description": "The tag of the container image." }, "imageFilePath": { "type": "string", "title": "Image File Path", "description": "The file path within the container image where the issue was found." }, "cweId": { "type": "string", "title": "CWE ID", "description": "The Common Weakness Enumeration (CWE) identifier related to the vulnerability." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: containers selector: query: 'true' port: entity: mappings: identifier: .id title: .id blueprint: '"checkmarxContainerSecurity"' properties: firstScanId: .firstScanId status: .status state: .state severity: .severity confidenceLevel: .confidenceLevel created: .created description: .description packageName: .data.packageName packageVersion: .data.packageVersion imageName: .data.imageName imageTag: .data.imageTag imageFilePath: .data.imageFilePath cweId: .vulnerabilityDetails.cweId relations: scan: .__scan_id ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxApiSec", "title": "Checkmarx API Security", "icon": "Checkmarx", "schema": { "properties": { "riskId": { "type": "string", "title": "Risk ID", "description": "The unique identifier for the API security risk." }, "apiId": { "type": "string", "title": "API ID", "description": "The identifier of the API associated with the risk." }, "severity": { "type": "string", "title": "Severity", "enum": [ "low", "medium", "high", "critical" ], "description": "The severity level of the API security risk." }, "name": { "type": "string", "title": "Name", "description": "The name of the API security risk." }, "status": { "type": "string", "title": "Status", "enum": [ "new", "recurrent", "fixed" ], "description": "The status of the API security risk." }, "httpMethod": { "type": "string", "title": "HTTP Method", "description": "The HTTP method associated with the API risk (e.g., GET, POST)." }, "url": { "type": "string", "title": "URL", "description": "The URL endpoint associated with the API risk." }, "origin": { "type": "string", "title": "Origin", "description": "The origin or source of the API risk." }, "documented": { "type": "boolean", "title": "Documented", "description": "Indicates whether the API is documented." }, "authenticated": { "type": "boolean", "title": "Authenticated", "description": "Indicates whether the API requires authentication." }, "discoveryDate": { "type": "string", "format": "date-time", "title": "Discovery Date", "description": "The date and time when the API risk was discovered." }, "scanId": { "type": "string", "title": "Scan ID", "description": "The identifier of the scan in which the risk was found." }, "sastRiskId": { "type": "string", "title": "SAST Risk ID", "description": "The identifier of the related SAST risk, if available." }, "projectId": { "type": "string", "title": "Project ID", "description": "The identifier of the project associated with the API risk." }, "state": { "type": "string", "title": "State", "enum": [ "to_verify", "confirmed", "urgent", "not_exploitable", "proposed_not_exploitable", "false_positive" ], "description": "The state of the API security risk." } }, "required": [ "riskId", "apiId", "name", "status", "state" ] }, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "many": false, "required": false } } } ``` Mapping configurationYAML ``` - kind: apisec selector: query: 'true' port: entity: mappings: identifier: .risk_id title: .name blueprint: '"checkmarxApiSec"' properties: riskId: .risk_id apiId: .api_id severity: (.severity // empty) name: .name status: .status httpMethod: .http_method url: .url origin: .origin documented: .documented authenticated: .authenticated discoveryDate: .discovery_date scanId: .scan_id sastRiskId: (.sast_risk_id // empty) projectId: .project_id state: .state relations: scan: .scan_id ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxApplication", "title": "Checkmarx Application", "icon": "Checkmarx", "schema": { "properties": { "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "The date and time when the application was created." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At", "description": "The date and time when the application was last updated." }, "tags": { "type": "object", "title": "Tags", "description": "A set of tags associated with the application." }, "rules": { "type": "object", "title": "Rules", "description": "A set of rules associated with the application." }, "description": { "type": "string", "title": "Description", "description": "The description of the application." }, "criticality": { "type": "number", "title": "Criticality", "description": "The criticality level of the project." } }, "required": [] }, "relations": { "projects": { "target": "checkmarxProject", "title": "Projects", "required": false, "many": true } } } ``` Mapping configurationYAML ``` - kind: application selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxApplication"' identifier: .id title: .name properties: description: .description createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) rules: .rules[] criticality: .criticality relations: projects: .projectIds ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxDastScanEnvironment", "title": "Checkmarx DAST Scan Environment", "icon": "Checkmarx", "schema": { "properties": { "domain": { "type": "string", "title": "Domain", "description": "The domain under scan." }, "url": { "type": "string", "title": "URL", "description": "The target URL of the environment." }, "created": { "type": "string", "format": "date-time", "title": "Created", "description": "The date when the environment was created." }, "scanType": { "type": "string", "title": "Scan Type", "description": "The type of scan (DAST)." }, "riskRating": { "type": "string", "title": "Risk Rating", "description": "Overall risk rating for the environment." }, "riskLevel": { "type": "object", "title": "Risk Level", "description": "Breakdown of risk counts by severity." }, "authMethod": { "type": "string", "title": "Auth Method", "description": "Authentication method used for scanning." }, "lastScanId": { "type": "string", "title": "Last Scan ID", "description": "The ID of the last DAST scan." }, "lastScanTime": { "type": "string", "format": "date-time", "title": "Last Scan Time", "description": "Timestamp of the last DAST scan." }, "lastStatus": { "type": "string", "title": "Last Status", "description": "The latest scan status." } }, "required": [ "url", "scanType" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Mapping configurationYAML ``` - kind: dast-scan-environment selector: query: 'true' port: entity: mappings: identifier: .environmentId title: .url blueprint: '"checkmarxDastScanEnvironment"' properties: domain: .domain url: .url created: .created scanType: .scanType riskRating: .riskRating riskLevel: .riskLevel authMethod: .authMethod lastScanId: .lastScanID lastScanTime: .lastScanTime lastStatus: .lastStatus ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxDastScan", "title": "Checkmarx DAST Scan", "icon": "Checkmarx", "schema": { "properties": { "environmentId": { "type": "string", "title": "Environment ID", "description": "Identifier of the related DAST environment." }, "initiator": { "type": "string", "title": "Initiator", "description": "User who initiated the scan." }, "scanType": { "type": "string", "title": "Scan Type", "description": "Type of scan (DAST)." }, "created": { "type": "string", "format": "date-time", "title": "Created", "description": "Timestamp when the scan was created." }, "startTime": { "type": "string", "format": "date-time", "title": "Start Time", "description": "When the scan started running." }, "updateTime": { "type": "string", "format": "date-time", "title": "Update Time", "description": "Last update timestamp for the scan." }, "lastStatus": { "type": "string", "title": "Last Status", "description": "Final or current status of the scan." }, "riskRating": { "type": "string", "title": "Risk Rating", "description": "Overall risk rating for the scan." }, "riskLevel": { "type": "object", "title": "Risk Level", "description": "Detailed risk breakdown." }, "scanDuration": { "type": "number", "title": "Scan Duration", "description": "Duration of the scan in seconds." } }, "required": [ "scanType" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "dastScanEnvironment": { "title": "DAST Scan Environment", "target": "checkmarxDastScanEnvironment", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: dast-scan selector: query: 'true' since: 20 port: entity: mappings: identifier: .scanId title: .scanId blueprint: '"checkmarxDastScan"' properties: environmentId: .environmentId initiator: .initiator scanType: .scanType created: .created startTime: .startTime updateTime: .updateTime lastStatus: .lastStatus riskRating: .riskRating riskLevel: .riskLevel scanDuration: .scanDuration relations: dastScanEnvironment: .__environment_id ``` BlueprintJSON Create in Port ``` { "identifier": "checkmarxDastScanResult", "title": "Checkmarx DAST Scan Result", "icon": "Checkmarx", "schema": { "properties": { "name": { "type": "string", "title": "Vulnerability Name", "description": "The name or title of the detected vulnerability." }, "status": { "type": "string", "title": "Status", "description": "The current status of the finding." }, "severity": { "type": "string", "title": "Severity", "description": "Severity level of the finding." }, "url": { "type": "string", "title": "URL", "description": "The URL where the issue was found." }, "method": { "type": "string", "title": "HTTP Method", "description": "HTTP method used in the request." }, "solution": { "type": "string", "title": "Solution", "description": "Recommended remediation or mitigation steps." }, "description": { "type": "string", "title": "Description", "description": "Details about the vulnerability." }, "cweId": { "type": "string", "title": "CWE ID", "description": "Common Weakness Enumeration identifier." }, "owasp": { "type": "array", "items": { "type": "string" }, "title": "OWASP References", "description": "OWASP Top 10 categories relevant to this issue." }, "environmentId": { "type": "string", "title": "Environment ID", "description": "ID of the environment where this result was found." } }, "required": [ "name", "severity", "url" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "dastScanEnvironment": { "title": "DAST Scan Environment", "target": "checkmarxDastScanEnvironment", "required": false, "many": false }, "dastScan": { "title": "DAST Scan", "target": "checkmarxDastScan", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: dast-scan-result selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxDastScanResult"' identifier: .id title: .name properties: name: .name status: .status severity: .severity url: .url method: .method solution: .solution description: .description cweId: .cwe_id owasp: .owasp environmentId: .environment_id relations: dastScanEnvironment: .environment_id dastScan: .scan_id ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Checkmarx One 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 Checkmarx One 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Checkmarx One is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Checkmarx One can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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.webhookSecret` parameter secures your webhooks. If not provided, the integration will process webhooks without validating the source of the events. ### Configure webhooks in Checkmarx One[​](#configure-webhooks-in-checkmarx-one "Direct link to Configure webhooks in Checkmarx One") To enable real-time updates from Checkmarx One to your Ocean integration, configure a webhook in your Checkmarx One instance. This allows Port to receive notifications when projects are created or scans complete, keeping your software catalog synchronized. Live events are supported for projects, scans, and all scan result types (SAST, SCA, KICS, Container Security, and API Security). You can configure webhooks at two levels: **tenant-level** for organization-wide monitoring, or **project-level** for specific projects. * Tenant-level (recommended) * Project-level Tenant-level webhooks receive events from all projects and support project creation events. For more details, see the [Checkmarx tenant-level webhooks documentation](https://docs.checkmarx.com/en/34965-378162-tenant-level-webhooks.html). 1. Log in to your Checkmarx One account with admin permissions. 2. Click the **gear icon** and select **Global Settings**. 3. Select **Webhooks** from the menu, then click **Add Webhook**. 4. Fill in the webhook details: * **Name**: Enter a descriptive name (e.g., `Port Integration Webhook`). * **Payload URL**: Enter the URL where your integration is accessible, following the pattern `https:///integration/webhook` (replace `` with your integration's host address). * **Secret** (optional): Enter a secret to secure webhook deliveries. If configured, set the same value in the `webhookSecret` parameter of your integration. * **Events**: Select `Project Created`, `Completed Scan`, `Failed Scan`, and `Partial Scan`. 5. Click **Add Webhook** to save. Project-level webhooks receive events only from a specific project. Use this for targeted monitoring of critical applications. For more details, see the [Checkmarx project webhooks documentation](https://docs.checkmarx.com/en/34965-68538-configuring-projects.html#UUID-1a1413d4-5d19-ddc0-aa35-51ff05ef0ade_UUID-a11c3b46-abfa-c26f-026b-0a9c04a79a46). 1. Navigate to the project you want to monitor. 2. Click the **three dots** menu on the project and select **Project Settings**. 3. Select **Webhooks** from the menu, then click **Add Webhook**. 4. Fill in the webhook details: * **Name**: Enter a descriptive name (e.g., `Port Integration Webhook`). * **Payload URL**: Enter the URL where your integration is accessible, following the pattern `https:///integration/webhook` (replace `` with your integration's host address). * **Secret** (optional): Enter a secret to secure webhook deliveries. If configured, set the same value in the `webhookSecret` parameter of your integration. * **Events**: Select `Completed Scan`, `Failed Scan`, and `Partial Scan`. 5. Click **Add Webhook** to save. Webhook security For added security, configure a webhook secret in Checkmarx One and set the same secret in your integration configuration using the `webhookSecret` parameter. This ensures that the integration only accepts legitimate events from Checkmarx One. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your Connectors page. Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found on the [Checkmarx examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/examples/.md). ### Resource Configuration[​](#resource-configuration "Direct link to Resource Configuration") The Checkmarx One integration supports filtering and configuration for different resource types: **Project resources** Projects can be synchronized without additional filtering options. **Scan resources** Scans can be filtered using the following scan filter options: * **Project Names** (`projectIds`): Filter scans by their project name * **Branches**: Filter results by the name of the Git branch that was scanned * **Statuses**: Filter results by the execution status of the scans (case insensitive, OR operator for multiple statuses): * `Queued` * `Running` * `Completed` * `Failed` * `Partial` * `Canceled` * **Since**: Filter results by the date and time when the scan was created (UNIX timestamp in seconds, default: 90 days) **Security Scan Results (SCA/Containers) configuration** You can configure security scan results (SCA, Container Security) with the following filters: * **Scan Filter**: Apply the same scan filtering options as above * **Severity**: Filter by severity level (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, `INFO`) * **State**: Filter by state: * `TO_VERIFY` * `CONFIRMED` * `URGENT` * `NOT_EXPLOITABLE` * `PROPOSED_NOT_EXPLOITABLE` * `FALSE_POSITIVE` * **Status**: Filter by status (`NEW`, `RECURRENT`, `FIXED`) * **Exclude Result Types**: Filter to exclude dev and test dependencies (`DEV_AND_TEST`, `NONE`) **DAST scan environments (`dast-scan-environment`)** DAST scan environments represent the target environments configured in Checkmarx One for DAST scanning. * **Selector**: No additional selector fields are required; use `query: 'true'`. **DAST scans (`dast-scan`) configuration** DAST scans are fetched per environment, and can be filtered with: * **Scan Type** (`scanType`): `DAST` or `DASTAPI` * **Since** (`since`): Number of days to look back for updated scans (1-90 days, default: 90) * **Max Results** (`maxResults`): Maximum number of scans to fetch per environment (1-3000, default: 3000) **DAST scan results (`dast-scan-result`) configuration** DAST scan results are fetched by enumerating DAST scan environments, then scans within each environment, and finally the results for each scan. This kind is **not included in the default mapping**, but you can add it to your `resources` block. You can configure DAST scan results with: * **DAST scan filter** (`dast_scan_filter`): * **Scan Type** (`scanType`): `DAST` or `DASTAPI` * **Since** (`since`): Number of days to look back for updated scans (1-90 days, default: 90) * **Max Results** (`maxResults`): Maximum number of scans to fetch per environment (1-3000, default: 3000) * **Result filters** (`filter`): * **Severity**: `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, `INFO` * **Status**: `NEW`, `RECURRENT` * **State**: * `TO_VERIFY` * `NOT_EXPLOITABLE` * `PROPOSED_NOT_EXPLOITABLE` * `CONFIRMED` * `URGENT` **SAST (Static Application Security Testing) configuration** SAST results can be configured with comprehensive filtering options: * **Scan Filter**: Apply the same scan filtering options as above * **Compliance**: Filter by compliance standard (exact match, case insensitive) * **Group**: Filter by vulnerability group (substring match) * **Include Nodes**: Include or omit node data (default: `true`) * **Language**: Filter by language (exact match, case insensitive) * **Result ID**: Filter by unique result hash * **Severity**: Filter by severity level (`critical`, `high`, `medium`, `low`, `info`) * **Status**: Filter by status (`new`, `recurrent`, `fixed`) * **Category**: Filter by comma separated list of categories * **State**: Filter by state: * `to_verify` * `not_exploitable` * `proposed_not_exploitable` * `confirmed` * `urgent` **KICS (Infrastructure as Code Security) configuration** KICS results can be configured with the following filters: * **Scan Filter**: Apply the same scan filtering options as above * **Severity**: Filter KICS results by severity levels (`CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, `INFO`) * **Status**: Filter KICS results by status (`NEW`, `RECURRENT`, `FIXED`) **API security configuration** API security results can be configured with: * **Scan Filter**: Apply the same scan filtering options as above **Application resources** Applications can be filtered using the following filter options: * **Tag Keys**: Filter results by the tag keys of the application * **Tag Values**: Filter results by the tag values of the application ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Common Issues[​](#common-issues "Direct link to Common Issues") 1. **Authentication Errors**: Ensure your API key is valid and has the correct permissions. 2. **Regional URL Issues**: Verify you're using the correct base URL and IAM URL for your region. 3. **Tenant Name Issues**: Make sure your tenant name is correctly specified. 4. **Permission Issues**: Ensure your API key has read access to projects, scans, and results. ### Logs and debugging[​](#logs-and-debugging "Direct link to Logs and debugging") The integration provides detailed logging for debugging: * Check the integration logs for authentication and API request details. * Verify that the correct URLs and credentials are being used. * Monitor for any rate limiting or permission errors. For more detailed troubleshooting, refer to the [Checkmarx One API documentation](https://docs.checkmarx.com/en/34965-68772-checkmarx-one-api-documentation.html). --- # Resource mapping examples ## Map Checkmarx projects[​](#map-checkmarx-projects "Direct link to Map Checkmarx projects") The following example demonstrates how to ingest your Checkmarx projects to Port: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxProject", "title": "Checkmarx Project", "icon": "Checkmarx", "schema": { "properties": { "name": { "type": "string", "title": "Project Name" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" }, "tags": { "type": "object", "title": "Tags" }, "repoUrl": { "type": "string", "title": "Repository URL" }, "mainBranch": { "type": "string", "title": "Main Branch" }, "origin": { "type": "string", "title": "Origin" }, "criticality": { "type": "number", "title": "Criticality" } }, "required": ["name"] }, "relations": {} } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality ``` ## Map Checkmarx scans[​](#map-checkmarx-scans "Direct link to Map Checkmarx scans") The following example demonstrates how to ingest your Checkmarx scans to Port: **Scan blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxScan", "title": "Checkmarx Scan", "icon": "Checkmarx", "schema": { "properties": { "status": { "type": "string", "title": "Status", "enum": [ "Queued", "Running", "Completed", "Failed", "Partial", "Canceled" ], "description": "The status of the scan. Possible values: Queued, Running, Completed, Failed, Partial, Canceled." }, "branch": { "type": "string", "title": "Branch", "description": "The branch of the repository that was scanned." }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "The date and time when the scan was created." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At", "description": "The date and time when the scan was last updated." }, "projectId": { "type": "string", "title": "Project ID", "description": "The identifier of the project to which this scan belongs." }, "userAgent": { "type": "string", "title": "User Agent", "description": "The user agent used to initiate the scan." }, "configs": { "type": "object", "title": "Configurations", "description": "Configuration details for the scan." }, "statusDetails": { "type": "array", "items": { "type": "object" }, "title": "Status Details", "description": "Detailed status information for the scan." } }, "required": ["status", "projectId"] }, "relations": { "project": { "title": "Project", "target": "checkmarxProject", "many": false, "required": true } } } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId ``` learn more * Scans represent individual security analysis runs for Checkmarx projects. * Each scan is linked to its parent project through relations. ## Map Checkmarx projects and scans together[​](#map-checkmarx-projects-and-scans-together "Direct link to Map Checkmarx projects and scans together") The following example demonstrates how to ingest both Checkmarx projects and their scans to Port in a single configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxProject", "title": "Checkmarx Project", "icon": "Checkmarx", "schema": { "properties": { "name": { "type": "string", "title": "Project Name" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" }, "tags": { "type": "object", "title": "Tags" }, "repoUrl": { "type": "string", "title": "Repository URL" }, "mainBranch": { "type": "string", "title": "Main Branch" }, "origin": { "type": "string", "title": "Origin" }, "criticality": { "type": "number", "title": "Criticality" } }, "required": ["name"] }, "relations": {} } ``` **Scan blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxScan", "title": "Checkmarx Scan", "icon": "Checkmarx", "schema": { "properties": { "status": { "type": "string", "title": "Status", "enum": [ "Queued", "Running", "Completed", "Failed", "Partial", "Canceled" ], "description": "The status of the scan. Possible values: Queued, Running, Completed, Failed, Partial, Canceled." }, "branch": { "type": "string", "title": "Branch", "description": "The branch of the repository that was scanned." }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "The date and time when the scan was created." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At", "description": "The date and time when the scan was last updated." }, "projectId": { "type": "string", "title": "Project ID", "description": "The identifier of the project to which this scan belongs." }, "userAgent": { "type": "string", "title": "User Agent", "description": "The user agent used to initiate the scan." }, "configs": { "type": "object", "title": "Configurations", "description": "Configuration details for the scan." }, "statusDetails": { "type": "array", "items": { "type": "object" }, "title": "Status Details", "description": "Detailed status information for the scan." } }, "required": ["status", "projectId"] }, "relations": { "project": { "title": "Project", "target": "checkmarxProject", "many": false, "required": true } } } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId ``` ## Map SAST findings[​](#map-sast-findings "Direct link to Map SAST findings") The following example demonstrates how to ingest Checkmarx SAST (Static Application Security Testing) findings to Port: **SAST blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxSast", "description": "Represents a Checkmarx Static Application Security Testing (SAST) finding in the catalogue", "title": "Checkmarx SASTs", "icon": "Checkmarx", "schema": { "properties": { "firstScanId": { "type": "string", "title": "First Scan ID", "description": "The identifier of the first scan in which this SAST finding appeared." }, "status": { "type": "string", "title": "Status", "description": "The status of the SAST finding", "enum": [ "NEW", "RECURRENT", "FIXED" ], "enumColors": { "NEW": "lightGray", "RECURRENT": "lightGray", "FIXED": "lightGray" } }, "state": { "icon": "DefaultProperty", "type": "string", "title": "State", "description": "The state of the SAST finding" }, "severity": { "type": "string", "description": "The severity level of the SAST finding", "title": "Severity", "enum": [ "LOW", "MEDIUM", "HIGH", "CRITICAL" ], "enumColors": { "LOW": "lightGray", "MEDIUM": "lightGray", "HIGH": "lightGray", "CRITICAL": "lightGray" } }, "confidenceLevel": { "type": "number", "title": "Confidence Level", "description": "The confidence level (0-100) assigned to the SAST finding", "minimum": 0, "maximum": 100 }, "created": { "type": "string", "title": "Created At", "description": "The date and time when the SAST finding was created", "format": "date-time" }, "description": { "type": "string", "title": "Description", "description": "The description of the SAST result" }, "nodes": { "items": { "type": "object" }, "icon": "DefaultProperty", "type": "array", "title": "Nodes", "description": "The list of nodes or code locations related to the SAST finding" }, "cweId": { "type": "string", "title": "CWE ID", "description": "The CWE (Common Weakness Enumeration) identifier associated with this SAST finding" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "required": false, "many": false } } } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId - kind: sast selector: query: 'true' port: entity: mappings: identifier: .resultHash title: .queryName blueprint: '"checkmarxSast"' properties: firstScanId: .firstScanID status: .status state: .state severity: (.severity // empty) confidenceLevel: .confidenceLevel created: .firstFoundAt nodes: (.nodes // empty) cweId: (.cweID // empty) relations: scan: .scanID ``` learn more * SAST findings represent static code analysis results from Checkmarx scans. * Each SAST finding is linked to the scan that discovered it. * The severity levels are: LOW, MEDIUM, HIGH, CRITICAL. ## Map SCA findings[​](#map-sca-findings "Direct link to Map SCA findings") The following example demonstrates how to ingest Checkmarx SCA (Software Composition Analysis) findings to Port: **SCA blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxSCA", "title": "Checkmarx SCA", "icon": "Checkmarx", "schema": { "properties": { "severity": { "type": "string", "title": "Severity", "description": "The severity level of the SCA finding." }, "state": { "type": "string", "title": "State", "description": "The state of the SCA finding." }, "description": { "type": "string", "title": "Description", "description": "A description of the SCA finding." }, "cweId": { "type": "string", "title": "CWE ID", "description": "The Common Weakness Enumeration (CWE) identifier related to the vulnerability." }, "status": { "type": "string", "title": "Status", "description": "The current status of the SCA finding." }, "created": { "type": "string", "format": "date-time", "title": "Created", "description": "The date and time when the SCA finding was created." }, "firstScanId": { "type": "string", "title": "First Scan ID", "description": "The identifier of the first scan in which this SCA issue was detected." }, "packageIdentifier": { "type": "string", "title": "Package Identifier", "description": "The identifier of the affected package." }, "recommendations": { "type": "array", "items": { "type": "string" }, "title": "Recommendations", "description": "Recommended actions to remediate the SCA finding." }, "recommendedVersion": { "type": "string", "title": "Recommended Version", "description": "The recommended version of the package to resolve the vulnerability." }, "packageData": { "items": { "type": "object" }, "type": "array", "title": "Package Data" }, "confidenceLevel": { "type": "string", "title": "Confidence Level", "description": "The confidence level of the SCA finding." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "required": false, "many": false } } } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId - kind: sca selector: query: 'true' port: entity: mappings: identifier: .alternateId title: .data.packageIdentifier blueprint: '"checkmarxSCA"' properties: firstScanId: .firstScanId status: .status state: .state severity: .severity created: .created description: .description packageIdentifier: .data.packageIdentifier recommendations: .data.recommendations recommendedVersion: .data.recommendedVersion packageData: .data.packageData cweId: .vulnerabilityDetails.cweId relations: scan: .__scan_id ``` learn more * SCA findings represent vulnerabilities in third-party dependencies and packages. * Each SCA finding includes package information and remediation recommendations. * The findings are linked to the scan that discovered them. ## Map KICS findings[​](#map-kics-findings "Direct link to Map KICS findings") The following example demonstrates how to ingest Checkmarx KICS (Keeping Infrastructure as Code Secure) findings to Port: **KICS blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxKics", "description": "Represents a KICS issue detected by Checkmarx One", "title": "Checkmarx KICS", "icon": "Checkmarx", "schema": { "properties": { "firstScanId": { "type": "string", "title": "First Scan ID", "description": "The identifier of the first scan in which this KICS issue was detected" }, "status": { "type": "string", "title": "Status", "description": "The current status of the KICS issue, indicating if it is new, recurring, or has been fixed.", "enum": [ "NEW", "RECURRENT", "FIXED" ], "enumColors": { "NEW": "lightGray", "RECURRENT": "lightGray", "FIXED": "lightGray" } }, "state": { "type": "string", "description": "The state of the KICS issue, reflecting its verification and remediation status", "title": "State" }, "severity": { "type": "string", "title": "Severity", "enum": [ "LOW", "MEDIUM", "HIGH", "CRITICAL", "INFO" ], "enumColors": { "LOW": "lightGray", "MEDIUM": "lightGray", "HIGH": "lightGray", "CRITICAL": "lightGray", "INFO": "lightGray" } }, "created": { "type": "string", "title": "Created At", "description": "The date and time when the KICS issue was first identified", "format": "date-time" }, "description": { "icon": "DefaultProperty", "type": "string", "title": "Description", "description": "A description of the KICS issue." }, "fileName": { "type": "string", "title": "File Name", "description": "The name of the file where the KICS issue was found." }, "line": { "type": "string", "title": "Line Number", "description": "The line number in the file where the KICS issue occurs" }, "platform": { "type": "string", "title": "Platform" }, "issueType": { "type": "string", "title": "Issue Type", "description": "The type or category of the KICS issue" }, "expectedValue": { "type": "string", "title": "Expected Value", "description": "The value that was expected in the configuration" }, "value": { "type": "string", "title": "Value", "description": "The actual value found in the configuration that triggered the KICS issue" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "required": false, "many": false } } } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId - kind: kics selector: query: 'true' port: entity: mappings: identifier: .id title: .description blueprint: '"checkmarxKics"' properties: type: .type firstScanId: .firstScanId id: .id status: .status state: .state severity: (.severity // empty) confidenceLevel: .confidenceLevel created: .created description: .description fileName: (.data.fileName // empty) line: (.data.line // empty) platform: (.data.platform // empty) issueType: (.data.issueType // empty) expectedValue: (.data.expectedValue // empty) value: (.data.value // empty) relations: scan: .__scan_id ``` learn more * KICS findings represent infrastructure as code security issues. * Each finding includes file location, expected vs actual values, and platform information. * The severity levels include: LOW, MEDIUM, HIGH, CRITICAL, INFO. ## Map Container Security findings[​](#map-container-security-findings "Direct link to Map Container Security findings") The following example demonstrates how to ingest Checkmarx Container Security findings to Port: **Container Security blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxContainerSecurity", "title": "Checkmarx Container Security", "icon": "Checkmarx", "schema": { "properties": { "firstScanId": { "type": "string", "title": "First Scan ID", "description": "The identifier of the first scan in which this container security issue was detected." }, "status": { "type": "string", "title": "Status", "description": "The current status of the container security finding." }, "state": { "type": "string", "title": "State", "description": "The state of the container security finding." }, "severity": { "type": "string", "title": "Severity", "description": "The severity level of the container security finding." }, "confidenceLevel": { "type": "string", "title": "Confidence Level", "description": "The confidence level of the container security finding." }, "created": { "type": "string", "format": "date-time", "title": "Created", "description": "The date and time when the container security finding was created." }, "description": { "type": "string", "title": "Description", "description": "A description of the container security finding." }, "packageName": { "type": "string", "title": "Package Name", "description": "The name of the affected package in the container." }, "packageVersion": { "type": "string", "title": "Package Version", "description": "The version of the affected package in the container." }, "imageName": { "type": "string", "title": "Image Name", "description": "The name of the container image." }, "imageTag": { "type": "string", "title": "Image Tag", "description": "The tag of the container image." }, "imageFilePath": { "type": "string", "title": "Image File Path", "description": "The file path within the container image where the issue was found." }, "cweId": { "type": "string", "title": "CWE ID", "description": "The Common Weakness Enumeration (CWE) identifier related to the vulnerability." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "required": false, "many": false } } } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId - kind: containers selector: query: 'true' port: entity: mappings: identifier: .__scan_id + "/" + .id title: .id blueprint: '"checkmarxContainerSecurity"' properties: firstScanId: .firstScanId status: .status state: .state severity: .severity confidenceLevel: .confidenceLevel created: .created description: .description packageName: .data.packageName packageVersion: .data.packageVersion imageName: .data.imageName imageTag: .data.imageTag imageFilePath: .data.imageFilePath cweId: .vulnerabilityDetails.cweId relations: scan: .__scan_id ``` learn more * Container Security findings represent vulnerabilities in container images. * Each finding includes package information, image details, and file paths. * Findings are linked to the scan that discovered them. ## Map API Security findings[​](#map-api-security-findings "Direct link to Map API Security findings") The following example demonstrates how to ingest Checkmarx API Security findings to Port: **API Security blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxApiSec", "title": "Checkmarx API Security", "icon": "Checkmarx", "schema": { "properties": { "riskId": { "type": "string", "title": "Risk ID", "description": "The unique identifier for the API security risk." }, "apiId": { "type": "string", "title": "API ID", "description": "The identifier of the API associated with the risk." }, "severity": { "type": "string", "title": "Severity", "enum": [ "low", "medium", "high", "critical" ], "description": "The severity level of the API security risk." }, "name": { "type": "string", "title": "Name", "description": "The name of the API security risk." }, "status": { "type": "string", "title": "Status", "enum": [ "new", "recurrent", "fixed" ], "description": "The status of the API security risk." }, "httpMethod": { "type": "string", "title": "HTTP Method", "description": "The HTTP method associated with the API risk (e.g., GET, POST)." }, "url": { "type": "string", "title": "URL", "description": "The URL endpoint associated with the API risk." }, "origin": { "type": "string", "title": "Origin", "description": "The origin or source of the API risk." }, "documented": { "type": "boolean", "title": "Documented", "description": "Indicates whether the API is documented." }, "authenticated": { "type": "boolean", "title": "Authenticated", "description": "Indicates whether the API requires authentication." }, "discoveryDate": { "type": "string", "format": "date-time", "title": "Discovery Date", "description": "The date and time when the API risk was discovered." }, "scanId": { "type": "string", "title": "Scan ID", "description": "The identifier of the scan in which the risk was found." }, "sastRiskId": { "type": "string", "title": "SAST Risk ID", "description": "The identifier of the related SAST risk, if available." }, "projectId": { "type": "string", "title": "Project ID", "description": "The identifier of the project associated with the API risk." }, "state": { "type": "string", "title": "State", "enum": [ "to_verify", "confirmed", "urgent", "not_exploitable", "proposed_not_exploitable", "false_positive" ], "description": "The state of the API security risk." } }, "required": [ "riskId", "apiId", "name", "status", "state" ] }, "relations": { "scan": { "title": "Scan", "target": "checkmarxScan", "many": false, "required": false } } } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxProject"' identifier: .id title: .name properties: name: .name createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) repoUrl: .repoUrl mainBranch: .mainBranch origin: .origin criticality: .criticality - kind: scan selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxScan"' identifier: .id title: '(.projectId + "-" + .id)' properties: status: .status branch: .branch createdAt: .createdAt updatedAt: .updatedAt projectId: .projectId userAgent: .userAgent configs: (.configs // {}) statusDetails: (.statusDetails // []) relations: project: .projectId - kind: apisec selector: query: 'true' port: entity: mappings: identifier: .risk_id title: .name blueprint: '"checkmarxApiSec"' properties: riskId: .risk_id apiId: .api_id severity: (.severity // empty) name: .name status: .status httpMethod: .http_method url: .url origin: .origin documented: .documented authenticated: .authenticated discoveryDate: .discovery_date scanId: .scan_id sastRiskId: (.sast_risk_id // empty) projectId: .project_id state: .state relations: scan: .scan_id ``` learn more * API Security findings represent risks discovered in API endpoints. * Each finding includes HTTP method, URL, authentication status, and documentation status. * The state field indicates the verification and remediation status. ## Map Checkmarx applications[​](#map-checkmarx-applications "Direct link to Map Checkmarx applications") The following example demonstrates how to ingest your Checkmarx applications to Port: **Application blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxApplication", "title": "Checkmarx Application", "icon": "Checkmarx", "schema": { "properties": { "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "The date and time when the application was created." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At", "description": "The date and time when the application was last updated." }, "tags": { "type": "object", "title": "Tags", "description": "A set of tags associated with the application." }, "rules": { "type": "object", "title": "Rules", "description": "A set of rules associated with the application." }, "description": { "type": "string", "title": "Description", "description": "The description of the application." }, "criticality": { "type": "number", "title": "Criticality", "description": "The criticality level of the project." } }, "required": [] }, "relations": { "projects": { "target": "checkmarxProject", "title": "Projects", "required": false, "many": true } } } ``` **Integration Mapping (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: application selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxApplication"' identifier: .id title: .name properties: description: .description createdAt: .createdAt updatedAt: .updatedAt tags: (.tags // {}) rules: .rules[] criticality: .criticality relations: projects: .projectIds ``` * Tag Keys * Tag Values The `tagKeys` optional selector is used to filter applications based on specific tag keys associated with your applications. ``` - kind: application selector: query: 'true' tagKeys: ['env', 'scope'] ``` The `tagValues` optional selector is used to filter applications based on specific tag values associated with your applications. ``` - kind: application selector: query: 'true' tagValues: ['dev', 'frontend'] ``` learn more * Applications in Checkmarx are one logical entity used to group related projects. * Each application can have multiple projects associated with it and having rules that define how projects are grouped. * For more information, see [Applications](https://checkmarx.stoplight.io/docs/checkmarx-one-api-reference-guide/branches/main/szojm2v0j748d-applications-service-rest-api) section in the Checkmarx One API Reference Guide. ## Map Dast Scan Environments, DAST scan and DAST scan results[​](#map-dast-scan-environments-dast-scan-and-dast-scan-results "Direct link to Map Dast Scan Environments, DAST scan and DAST scan results") The following example demonstrates how to ingest Checkmarx DAST scan environments, scans, and scan results to Port: **DAST scan environment blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxDastScanEnvironment", "title": "Checkmarx DAST Scan Environment", "icon": "Checkmarx", "schema": { "properties": { "domain": { "type": "string", "title": "Domain", "description": "The domain under scan." }, "url": { "type": "string", "title": "URL", "description": "The target URL of the environment." }, "created": { "type": "string", "format": "date-time", "title": "Created", "description": "The date when the environment was created." }, "scanType": { "type": "string", "title": "Scan Type", "description": "The type of scan (DAST)." }, "riskRating": { "type": "string", "title": "Risk Rating", "description": "Overall risk rating for the environment." }, "riskLevel": { "type": "object", "title": "Risk Level", "description": "Breakdown of risk counts by severity." }, "authMethod": { "type": "string", "title": "Auth Method", "description": "Authentication method used for scanning." }, "lastScanId": { "type": "string", "title": "Last Scan ID", "description": "The ID of the last DAST scan." }, "lastScanTime": { "type": "string", "format": "date-time", "title": "Last Scan Time", "description": "Timestamp of the last DAST scan." }, "lastStatus": { "type": "string", "title": "Last Status", "description": "The latest scan status." } }, "required": [ "url", "scanType" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **DAST scan blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxDastScan", "title": "Checkmarx DAST Scan", "icon": "Checkmarx", "schema": { "properties": { "environmentId": { "type": "string", "title": "Environment ID", "description": "Identifier of the related DAST environment." }, "initiator": { "type": "string", "title": "Initiator", "description": "User who initiated the scan." }, "scanType": { "type": "string", "title": "Scan Type", "description": "Type of scan (DAST)." }, "created": { "type": "string", "format": "date-time", "title": "Created", "description": "Timestamp when the scan was created." }, "startTime": { "type": "string", "format": "date-time", "title": "Start Time", "description": "When the scan started running." }, "updateTime": { "type": "string", "format": "date-time", "title": "Update Time", "description": "Last update timestamp for the scan." }, "lastStatus": { "type": "string", "title": "Last Status", "description": "Final or current status of the scan." }, "riskRating": { "type": "string", "title": "Risk Rating", "description": "Overall risk rating for the scan." }, "riskLevel": { "type": "object", "title": "Risk Level", "description": "Detailed risk breakdown." }, "scanDuration": { "type": "number", "title": "Scan Duration", "description": "Duration of the scan in seconds." } }, "required": [ "scanType" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "dastScanEnvironment": { "title": "DAST Scan Environment", "target": "checkmarxDastScanEnvironment", "required": false, "many": false } } } ``` **DAST scan result blueprint (click to expand)** Create in Port ``` { "identifier": "checkmarxDastScanResult", "title": "Checkmarx DAST Scan Result", "icon": "Checkmarx", "schema": { "properties": { "name": { "type": "string", "title": "Vulnerability Name", "description": "The name or title of the detected vulnerability." }, "status": { "type": "string", "title": "Status", "description": "The current status of the finding." }, "severity": { "type": "string", "title": "Severity", "description": "Severity level of the finding." }, "url": { "type": "string", "title": "URL", "description": "The URL where the issue was found." }, "method": { "type": "string", "title": "HTTP Method", "description": "HTTP method used in the request." }, "solution": { "type": "string", "title": "Solution", "description": "Recommended remediation or mitigation steps." }, "description": { "type": "string", "title": "Description", "description": "Details about the vulnerability." }, "cweId": { "type": "string", "title": "CWE ID", "description": "Common Weakness Enumeration identifier." }, "owasp": { "type": "array", "items": { "type": "string" }, "title": "OWASP References", "description": "OWASP Top 10 categories relevant to this issue." }, "environmentId": { "type": "string", "title": "Environment ID", "description": "ID of the environment where this result was found." } }, "required": [ "name", "severity", "url" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "dastScanEnvironment": { "title": "DAST Scan Environment", "target": "checkmarxDastScanEnvironment", "required": false, "many": false }, "dastScan": { "title": "DAST Scan", "target": "checkmarxDastScan", "required": false, "many": false } } } ``` **Integration Mapping (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: dast-scan-environment selector: query: 'true' port: entity: mappings: identifier: .environmentId title: .url blueprint: '"checkmarxDastScanEnvironment"' properties: domain: .domain url: .url created: .created scanType: .scanType riskRating: .riskRating riskLevel: .riskLevel authMethod: .authMethod lastScanId: .lastScanID lastScanTime: .lastScanTime lastStatus: .lastStatus - kind: dast-scan selector: query: 'true' since: 20 port: entity: mappings: identifier: .scanId title: .scanId blueprint: '"checkmarxDastScan"' properties: environmentId: .environmentId initiator: .initiator scanType: .scanType created: .created startTime: .startTime updateTime: .updateTime lastStatus: .lastStatus riskRating: .riskRating riskLevel: .riskLevel scanDuration: .scanDuration relations: dastScanEnvironment: .__environment_id - kind: dast-scan-result selector: query: 'true' port: entity: mappings: blueprint: '"checkmarxDastScanResult"' identifier: .id title: .name properties: name: .name status: .status severity: .severity url: .url method: .method solution: .solution description: .description cweId: .cwe_id owasp: .owasp environmentId: .environment_id relations: dastScanEnvironment: .environment_id dastScan: .scan_id ``` learn more * DAST scan results are fetched by discovering DAST environments, then scans, then results per scan. --- # Mend.io ![Mend icon](/img/guides/icons/Mend.svg)![Mend icon](/img/guides/icons/Mend.svg) Loading version... Port's Mend.io integration allows you to model Mend projects and SCA security findings in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://api-docs.mend.io/platform/3.0/projects/getprojectsummaries)[SCA finding](https://api-docs.mend.io/platform/3.0/findings-project/getsecurityvulnerabilityfindings) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A Mend.io account with the **Organization Administrator** role in the organization you want to sync. * A Port organization with admin permissions. * A Mend.io activation key from the Port integration card in Mend. ### Obtain the Mend activation key[​](#obtain-the-mend-activation-key "Direct link to Obtain the Mend activation key") 1. Log in to your **Mend Platform** tenant as an organization administrator, using your organization's Mend sign-in URL or SSO flow. 2. Click the **cog icon** to open **Settings**. 3. Select **Integrations**. 4. Under **Third-Party Platforms**, click the **Port.io** card. 5. Copy the activation key. The activation key includes the integrator user, user key, Mend environment URL, and organization UUID. The integration derives the rest of the required Mend connection details from it. For more information, see the [Mend documentation](https://docs.mend.io/platform/latest/integrate-mend-results-into-port-io). ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Periodic polling with configurable resync intervals * ✧Best for high-security or custom networking needs Install Activation key validation A malformed or truncated activation key causes the integration to fail with `Provide a valid Mend Activation key`. If you see this error, copy the full activation key from Mend again and update the integration credentials. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#yaml-editor) to ingest data from the third-party API into Port. The mapping uses the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform, and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true entityDeletionThreshold: 0 resources: - kind: mend-project selector: query: 'true' port: entity: mappings: identifier: .uuid title: .name blueprint: '"mendProject"' properties: applicationName: .path applicationUuid: .applicationUuid createdAt: .creationDate languages: '(.statistics.LIBRARY_TYPE_HISTOGRAM // {} | keys)' lastScanned: 'if (.statistics.LAST_SCAN.lastScanTime // 0) > 0 then (.statistics.LAST_SCAN.lastScanTime / 1000 | todate) else null end' totalCritical: '((.statistics.ALERTS.criticalSeverityVulnerabilities // 0) + (.statistics.IMG_SECURITY.imgCriticalVulnerabilities // 0))' totalHigh: '((.statistics.ALERTS.highSeverityVulnerabilities // 0) + (.statistics.IMG_SECURITY.imgHighVulnerabilities // 0) + (.statistics.SAST_VULNERABILITIES_BY_SEVERITY.sastHighVulnerabilities // 0))' totalMedium: '((.statistics.ALERTS.mediumSeverityVulnerabilities // 0) + (.statistics.IMG_SECURITY.imgMediumVulnerabilities // 0) + (.statistics.SAST_VULNERABILITIES_BY_SEVERITY.sastMediumVulnerabilities // 0))' totalLow: '((.statistics.ALERTS.lowSeverityVulnerabilities // 0) + (.statistics.IMG_SECURITY.imgLowVulnerabilities // 0) + (.statistics.SAST_VULNERABILITIES_BY_SEVERITY.sastLowVulnerabilities // 0))' dependencyFindingStats: .statistics.ALERTS codeFindingStats: .statistics.SAST_VULNERABILITIES_BY_SEVERITY containerFindingStats: .statistics.IMG_SECURITY - kind: sca-finding selector: query: 'true' port: entity: mappings: identifier: .uuid title: .name blueprint: '"mendSCA"' properties: status: .findingInfo.findingStatus severity: .vulnerability.severity violations: if (.violations // 0) > 0 then true else false end reachability: .reachability malicious: if has("malicious") then .malicious else false end description: .vulnerability.description componentType: .component.componentType componentName: .component.name publishDate: .vulnerability.publishDate detectedAt: .findingInfo.detectedAt modifiedAt: .findingInfo.modifiedAt cvssType: .vulnerability.type cvssScore: .vulnerability.score exploitMaturity: .threatAssessment.exploitCodeMaturity epssScore: .threatAssessment.epssPercentage cveLink: 'if .name then "https://cve.mitre.org/cgi-bin/cvename.cgi?name=" + .name else null end' mendLink: 'if .name then "https://www.mend.io/vulnerability-database/" + .name else null end' topFix: .topFix type: .component.dependencyType suppressionReason: 'if .findingInfo.findingStatus == "SUPPRESSED" then .findingInfo.comment.comment else "" end' suppressionDate: 'if .findingInfo.findingStatus == "SUPPRESSED" then .findingInfo.comment.date else null end' relations: project: .project.uuid ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view the default mapping and blueprint for each resource type. ProjectSCA finding BlueprintJSON Create in Port ``` { "identifier": "mendProject", "title": "Mend Project", "icon": "Mend", "schema": { "properties": { "applicationName": { "type": "string", "title": "Application Name" }, "applicationUuid": { "type": "string", "title": "Application UUID" }, "totalCritical": { "type": "number", "title": "Critical Findings" }, "totalHigh": { "type": "number", "title": "High Findings" }, "totalMedium": { "type": "number", "title": "Medium Findings" }, "totalLow": { "type": "number", "title": "Low Findings" }, "languages": { "type": "array", "items": { "type": "string" }, "title": "Languages" }, "createdAt": { "type": "string", "format": "date-time", "title": "Creation Date" }, "lastScanned": { "type": "string", "format": "date-time", "title": "Last Scanned" }, "dependencyFindingStats": { "type": "object", "title": "Dependency Finding Stats" }, "codeFindingStats": { "type": "object", "title": "Code Finding Stats" }, "containerFindingStats": { "type": "object", "title": "Container Finding Stats" } }, "required": [] }, "relations": {} } ``` Default mappingYAML ``` - kind: mend-project selector: query: 'true' port: entity: mappings: identifier: .uuid title: .name blueprint: '"mendProject"' properties: applicationName: .path applicationUuid: .applicationUuid createdAt: .creationDate languages: '(.statistics.LIBRARY_TYPE_HISTOGRAM // {} | keys)' lastScanned: 'if (.statistics.LAST_SCAN.lastScanTime // 0) > 0 then (.statistics.LAST_SCAN.lastScanTime / 1000 | todate) else null end' totalCritical: '((.statistics.ALERTS.criticalSeverityVulnerabilities // 0) + (.statistics.IMG_SECURITY.imgCriticalVulnerabilities // 0))' totalHigh: '((.statistics.ALERTS.highSeverityVulnerabilities // 0) + (.statistics.IMG_SECURITY.imgHighVulnerabilities // 0) + (.statistics.SAST_VULNERABILITIES_BY_SEVERITY.sastHighVulnerabilities // 0))' totalMedium: '((.statistics.ALERTS.mediumSeverityVulnerabilities // 0) + (.statistics.IMG_SECURITY.imgMediumVulnerabilities // 0) + (.statistics.SAST_VULNERABILITIES_BY_SEVERITY.sastMediumVulnerabilities // 0))' totalLow: '((.statistics.ALERTS.lowSeverityVulnerabilities // 0) + (.statistics.IMG_SECURITY.imgLowVulnerabilities // 0) + (.statistics.SAST_VULNERABILITIES_BY_SEVERITY.sastLowVulnerabilities // 0))' dependencyFindingStats: .statistics.ALERTS codeFindingStats: .statistics.SAST_VULNERABILITIES_BY_SEVERITY containerFindingStats: .statistics.IMG_SECURITY ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | ------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | BlueprintJSON Create in Port ``` { "identifier": "mendSCA", "title": "Mend SCA", "icon": "Mend", "schema": { "properties": { "status": { "type": "string", "title": "Status", "enum": ["UNREVIEWED", "IN_REVIEW", "REMEDIATED", "SUPPRESSED"], "enumColors": { "UNREVIEWED": "blue", "IN_REVIEW": "yellow", "REMEDIATED": "green", "SUPPRESSED": "darkGray" } }, "severity": { "type": "string", "title": "Severity", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"], "enumColors": { "CRITICAL": "red", "HIGH": "orange", "MEDIUM": "yellow", "LOW": "blue" } }, "violations": { "type": "boolean", "title": "Violations" }, "reachability": { "type": "string", "title": "Reachability", "enum": [ "REACHABLE", "POTENTIALLY_REACHABLE", "UNREACHABLE", "REACHABILITY_UNAVAILABLE" ], "enumColors": { "REACHABLE": "red", "POTENTIALLY_REACHABLE": "yellow", "UNREACHABLE": "green", "REACHABILITY_UNAVAILABLE": "darkGray" } }, "malicious": { "type": "boolean", "title": "Malicious" }, "description": { "type": "string", "title": "Description" }, "componentType": { "type": "string", "title": "Component Type" }, "componentName": { "type": "string", "title": "Component Name / Origin" }, "cvssType": { "type": "string", "title": "CVSS Type", "enum": ["CVSS_2", "CVSS_3", "CVSS_4"], "enumColors": { "CVSS_2": "yellow", "CVSS_3": "blue", "CVSS_4": "green" } }, "cvssScore": { "type": "number", "title": "CVSS Score" }, "type": { "type": "string", "title": "Type", "enum": ["Direct", "Transitive", "Direct / Transitive"], "enumColors": { "Direct": "red", "Transitive": "orange", "Direct / Transitive": "blue" } }, "exploitMaturity": { "type": "string", "title": "Exploit Maturity", "enum": ["HIGH", "FUNCTIONAL", "POC_CODE", "UNPROVEN", "NOT_DEFINED"], "enumColors": { "HIGH": "red", "FUNCTIONAL": "orange", "POC_CODE": "yellow", "UNPROVEN": "green", "NOT_DEFINED": "darkGray" } }, "epssScore": { "type": "number", "title": "EPSS Score %" }, "cveLink": { "type": "string", "format": "url", "title": "CVE Link", "icon": "Vulnerability" }, "mendLink": { "type": "string", "format": "url", "title": "Mend Link" }, "topFix": { "type": "object", "title": "Remediation" }, "suppressionReason": { "type": "string", "title": "Suppression Reason" }, "suppressionDate": { "type": "string", "format": "date-time", "title": "Suppression Date" }, "publishDate": { "type": "string", "format": "date-time", "title": "Published" }, "detectedAt": { "type": "string", "format": "date-time", "title": "Detected" }, "modifiedAt": { "type": "string", "format": "date-time", "title": "Modified" } }, "required": [] }, "relations": { "project": { "title": "Project", "target": "mendProject", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: sca-finding selector: query: 'true' port: entity: mappings: identifier: .uuid title: .name blueprint: '"mendSCA"' properties: status: .findingInfo.findingStatus severity: .vulnerability.severity violations: if (.violations // 0) > 0 then true else false end reachability: .reachability malicious: if has("malicious") then .malicious else false end description: .vulnerability.description componentType: .component.componentType componentName: .component.name publishDate: .vulnerability.publishDate detectedAt: .findingInfo.detectedAt modifiedAt: .findingInfo.modifiedAt cvssType: .vulnerability.type cvssScore: .vulnerability.score exploitMaturity: .threatAssessment.exploitCodeMaturity epssScore: .threatAssessment.epssPercentage cveLink: 'if .name then "https://cve.mitre.org/cgi-bin/cvename.cgi?name=" + .name else null end' mendLink: 'if .name then "https://www.mend.io/vulnerability-database/" + .name else null end' topFix: .topFix type: .component.dependencyType suppressionReason: 'if .findingInfo.findingStatus == "SUPPRESSED" then .findingInfo.comment.comment else "" end' suppressionDate: 'if .findingInfo.findingStatus == "SUPPRESSED" then .findingInfo.comment.date else null end' relations: project: .project.uuid ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | ------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `severity` | `array` | Filter security findings by severity level. Accepted values: CRITICAL, HIGH, MEDIUM, LOW. | ## Configure sync behavior[​](#configure-sync-behavior "Direct link to Configure sync behavior") Mend currently has no first-class webhook support, so this integration relies on scheduled polling resyncs. Each ordinary poll performs a delta sync. It fetches findings only for projects whose last scan time or creation date changed since the last successful sync. The `fullSyncIntervalMinutes` parameter controls periodic forced full resyncs. The default is `720` minutes, or 12 hours.
A forced full resync bypasses the delta filter and picks up finding-only changes such as status transitions, suppressions, and edited fixes. Set `fullSyncIntervalMinutes` to `0` if every poll should perform a full sync. ## Limitations[​](#limitations "Direct link to Limitations") * Mend changes appear in Port only after the next scheduled resync. * Finding-only changes are picked up by the next forced full resync, which runs every 12 hours by default. * The default mapping uses `entityDeletionThreshold: 0` because delta syncs intentionally skip unchanged projects. Raise this value only if your full sync interval enumerates all findings often enough for stale entity cleanup. * Each installation syncs one Mend organization. To sync multiple organizations, install one integration instance per organization. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") Use **Interactive mode** to be guided through diagnostic checks, or **Quick reference** to scan the full list of issues and fixes. Interactive modeQuick reference ### What are you experiencing? Select the issue that best matches your situation. We'll guide you through diagnosing and fixing it step by step. Activation key validation fails The integration fails during startup with an invalid activation key error → Persistent 401 authentication errors The integration starts but Mend API requests return authentication errors → No Mend entities appear in Port The integration syncs successfully but projects or SCA findings are missing → Status or suppression changes are delayed A Mend finding changed, but the matching Port entity still shows the old state → Deleted findings remain in Port Findings removed from Mend still appear as entities in Port → Resyncs are slow Mend resyncs take longer than expected or put too much load on the integration → For more help, visit the [Port community](https://port.io/community) or open a support ticket. --- # Snyk ![Snyk icon](/img/guides/icons/Snyk.svg)![Snyk icon](/img/guides/icons/Snyk.svg) Loading version... Port's Snyk integration allows you to model Snyk resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Organization](https://apidocs.snyk.io/?version=2024-10-15#get-/orgs)[Target](https://apidocs.snyk.io/?version=2024-10-15#get-/orgs/-org_id-/targets)[Project](https://apidocs.snyk.io/?version=2024-10-15#get-/orgs/-org_id-/projects)[Vulnerability](https://apidocs.snyk.io/?version=2024-10-15#get-/orgs/-org_id-/issues)[Issue](https://apidocs.snyk.io/?version=2024-10-15#get-/orgs/-org_id-/issues)[Policy](https://apidocs.snyk.io/?version=2026-03-25#get-/orgs/-org_id-/policies) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A Snyk enterprise account. * A Port organization with admin permissions. Snyk Enterprise The Snyk API is available for Enterprise customers only. Authentication using personal tokens from Snyk Free or Team plans is not supported. [Learn More](https://docs.snyk.io/snyk-api/authentication-for-api). ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: organization selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"snykOrganization"' properties: slug: .attributes.slug url: ("https://app.snyk.io/org/" + .attributes.slug | tostring) - kind: project selector: query: 'true' attachIssuesToProject: 'false' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"snykProject"' properties: url: ("https://app.snyk.io/org/" + .__organization.slug + "/project/" + .id | tostring) businessCriticality: .attributes.business_criticality environment: .attributes.environment lifeCycle: .attributes.lifecycle highOpenVulnerabilities: .meta.latest_issue_counts.high mediumOpenVulnerabilities: .meta.latest_issue_counts.medium lowOpenVulnerabilities: .meta.latest_issue_counts.low criticalOpenVulnerabilities: .meta.latest_issue_counts.critical tags: .attributes.tags targetOrigin: .attributes.origin relations: snyk_target: '.relationships.target.data.id' - kind: target selector: query: 'true' attachProjectData: true apiQueryParams: exclude_empty: false # set to false to include targets with no associated projects port: entity: mappings: identifier: .id title: .attributes.display_name blueprint: '"snykTarget"' properties: origin: .relationships.integration.data.attributes.integration_type relations: snyk_organization: '.relationships.organization.data.id' - kind: vulnerability selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.title blueprint: '"snykVulnerability"' properties: score: .attributes.risk.score.value packageNames: '[.attributes.coordinates[].representations[].dependency?.package_name | select(. != null)]' packageVersions: '[.attributes.coordinates[].representations[].dependency?.package_version | select(. != null)]' severity: .attributes.effective_severity_level url: ("https://app.snyk.io/org/" + .__organization.slug + "/project/" + .relationships.scan_item.data.id + "#issue-" + .attributes.key | tostring) publicationTime: .attributes.created_at status: .attributes.status type: .attributes.type relations: project: .relationships.scan_item.data.id ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. OrganizationTargetProjectVulnerabilityPolicy Default mappingYAML ``` - kind: organization selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"snykOrganization"' properties: slug: .attributes.slug url: ("https://app.snyk.io/org/" + .attributes.slug | tostring) ``` Sample payloadJSON ``` { "id": "a37b1727-ed7c-4f2c-9b59-7c5908e", "type": "org", "attributes": { "group_id": "c7ec7fa4-50ae-450f-8b48-49270cfc54b5", "is_personal": false, "name": "NFR - Shared", "slug": "nfr-shared", "created_at": "2023-08-14T18:34:58Z", "updated_at": "2025-08-22T10:09:27Z" }, "relationships": { "member_role": { "data": { "id": "d8ba290b-aa9e-4957-9f0b-9409d7b4", "type": "org_role" } } }, "group_id": "c7ec7fa4-50ae-450f-8b48-49270cfc", "is_personal": false, "name": "NFR - Shared", "slug": "nfr-shared", "created_at": "2023-08-14T18:34:58Z", "updated_at": "2025-08-22T10:09:27Z" } ``` Default mappingYAML ``` - kind: target selector: query: 'true' attachProjectData: true apiQueryParams: exclude_empty: false # set to false to include targets with no associated projects port: entity: mappings: identifier: .id title: .attributes.display_name blueprint: '"snykTarget"' properties: origin: .relationships.integration.data.attributes.integration_type relations: snyk_organization: '.relationships.organization.data.id' ``` Target Resync Behavior When ingesting a Snyk target, the integration can also fetch all organization projects to enrich the data. In large organizations, this org-wide fetch may cause performance issues and rate-limit errors. You can control this behavior using the `attachProjectData` flag in your Target selector. By default, `attachProjectData` is set to true for backwards compatibility. To optimize performance and prevent 429 errors, we strongly advise setting `attachProjectData: false` in your Target selector. The integration will safely skip the org-wide project fetch, and the project-to-target relationship will still be correctly maintained via the project mapping. Sample payloadJSON ``` { "attributes": { "created_at": "2022-09-01T00:00:00Z", "display_name": "snyk-fixtures/goof", "is_private": false, "url": "http://github.com/snyk/local-goof" }, "id": "55a348e2-c3ad-4bbc-b40e-9b232d1f4121", "relationships": { "integration": { "data": { "attributes": { "integration_type": "gitlab" }, "id": "7667dae6-602c-45d9-baa9-79e1a640f199", "type": "integration" } }, "organization": { "data": { "id": "e661d4ef-5ad5-4cef-ad16-5157cefa83f5", "type": "organization" } } }, "type": "target" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `attachProjectData` | `boolean` | Whether to attach project data to the target during ingestion. Default: `true`. | | `apiQueryParams` | `object` | Snyk target API query params. For more information see the [Snyk API docs](https://docs.snyk.io/snyk-api/reference/targets#get-orgs-org_id-targets). | `apiQueryParams` parameters | Parameter | Type | Description | | --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `exclude_empty` | `boolean` | When false, returns all targets including those with no associated projects. When true, returns only targets with at least one project. | | `is_private` | `boolean` | Return targets that match the provided value of is\_private. | | `url` | `string` | Return targets that match the provided remote URL. | | `display_name` | `string` | Return targets with display names starting with the provided string. | | `created_gte` | `string` | Return only targets created at or after the specified date. Must be RFC3339 with timezone (e.g. 2022-01-01T16:00:00Z). | Default mappingYAML ``` - kind: project selector: query: 'true' attachIssuesToProject: 'false' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"snykProject"' properties: url: ("https://app.snyk.io/org/" + .__organization.slug + "/project/" + .id | tostring) businessCriticality: .attributes.business_criticality environment: .attributes.environment lifeCycle: .attributes.lifecycle highOpenVulnerabilities: .meta.latest_issue_counts.high mediumOpenVulnerabilities: .meta.latest_issue_counts.medium lowOpenVulnerabilities: .meta.latest_issue_counts.low criticalOpenVulnerabilities: .meta.latest_issue_counts.critical tags: .attributes.tags targetOrigin: .attributes.origin relations: snyk_target: '.relationships.target.data.id' ``` Sample payloadJSON ``` { "type": "project", "id": "4db4ea77-1bb5-43e3-9542-3db993c8e", "meta": { "latest_issue_counts": { "updated_at": "2025-11-12T01:32:36.078Z", "critical": 0, "high": 0, "medium": 0, "low": 0 } }, "attributes": { "name": "experimental/Py:src/requirements.txt", "type": "pip", "target_file": "src/requirements.txt", "target_reference": "main", "origin": "github", "created": "2025-08-01T04:50:31.081Z", "status": "active", "business_criticality": [], "environment": [], "lifecycle": [], "tags": [], "read_only": false, "settings": { "recurring_tests": { "frequency": "daily" }, "pull_requests": {} } }, "relationships": { "organization": { "data": { "type": "org", "id": "a37b1727-9dc0-4f2c-9b59-7c5908e4d4d2" }, "links": { "related": "/rest/orgs/a37b1727-9dc0-4f2c-9b59-7c5908e4d4d2" } }, "target": { "data": { "id": "d25f0f1b-c45c-40b5-9dc2-d04b8a4a659f", "type": "target", "attributes": { "display_name": "experimental/Py", "url": "https://github.com/experimental/Py" }, "meta": { "integration_data": { "id": 929419086, "name": "Py", "owner": "experimental" } } }, "links": { "related": "/rest/orgs/a37b1727-9dc0-4f2c-9b59-7c5908e4d4d2/targets/d25f0f1b-c45c-40b5-9dc2-d04b8a4a" } }, "importer": { "data": { "type": "user", "id": "412b9360-5ae2-48d6-8d47-a9e27a10ff" }, "links": { "related": "/rest/orgs/a37b1727-9dc0-4f2c-9b59-7c5908e4d4d2/users/412b9360-5ae2-48d6-8d47-a9e27a10" } } }, "__organization": { "id": "a37b1727-9dc0-4f2c-9b59-7c5908e4", "type": "org", "attributes": { "group_id": "c7ec7fa4-50ae-450f-8b48-49270cfc5", "is_personal": false, "name": "Port NFR - Shared", "slug": "port-nfr-shared", "created_at": "2023-08-14T18:34:58Z", "updated_at": "2025-08-22T10:09:27Z" }, "relationships": { "member_role": { "data": { "id": "d8ba290b-aa9e-4957-9f0b-9409d7b4f", "type": "org_role" } } }, "group_id": "c7ec7fa4-50ae-450f-8b48-49270cfc", "is_personal": false, "name": "NFR - Shared", "slug": "nfr-shared", "created_at": "2023-08-14T18:34:58Z", "updated_at": "2025-08-22T10:09:27Z" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ----------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `attachIssuesToProject` | `boolean` | Whether to attach issues to the project during ingestion. Default: `true`. | | `apiQueryParams` | `object` | Snyk project API query params, for more information please look at the [Snyk API docs](https://docs.snyk.io/snyk-api/reference/projects) | `apiQueryParams` parameters | Parameter | Type | Description | | ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `target_id` | `array` | Only return projects in the provided targets | | `target_reference` | `string` | Return projects that match the provided target reference | | `target_file` | `string` | Return projects that match the provided target file | | `target_runtime` | `string` | Return projects that match the provided target runtime | | `names` | `array` | Return projects that match the provided names | | `names_start_with` | `array` | Return projects with names starting with the specified prefix. | | `origins` | `array` | Return projects that match the provided origins. | | `types` | `array` | Return projects of the provided type (e.g. npm, pip, docker) | | `tags` | `array` | Return projects that match all the provided tags (key=value format). Tag values must not contain commas. | | `business_criticality` | `array` | Return projects with the provided business criticality Allowed values: `critical`, `high`, `medium`, `low`. | | `environment` | `array` | Return projects with the provided environment Allowed values: `frontend`, `backend`, `internal`, `external`, `mobile`, `saas`, `onprem`, `hosted`, `distributed`. | | `lifecycle` | `array` | Return projects with the provided lifecycle Allowed values: `production`, `development`, `sandbox`. | | `ids` | `array` | Return only projects with the provided IDs | Default mappingYAML ``` - kind: vulnerability selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.title blueprint: '"snykVulnerability"' properties: score: .attributes.risk.score.value packageNames: '[.attributes.coordinates[].representations[].dependency?.package_name | select(. != null)]' packageVersions: '[.attributes.coordinates[].representations[].dependency?.package_version | select(. != null)]' severity: .attributes.effective_severity_level url: ("https://app.snyk.io/org/" + .__organization.slug + "/project/" + .relationships.scan_item.data.id + "#issue-" + .attributes.key | tostring) publicationTime: .attributes.created_at status: .attributes.status type: .attributes.type relations: project: .relationships.scan_item.data.id ``` Sample payloadJSON ``` { "id": "ff2dc68c-8889-431c-91d1-6ed46dfd2", "type": "issue", "attributes": { "classes": [ { "id": "CWE-506", "source": "CWE", "type": "weakness" } ], "coordinates": [ { "is_fixable_manually": false, "is_fixable_snyk": false, "is_fixable_upstream": false, "is_patchable": false, "is_pinnable": false, "is_upgradeable": false, "reachability": "no-info", "representations": [ { "dependency": { "package_name": "techdocs-cli-embedded-app", "package_version": "0.2.83-next.2" } } ] } ], "created_at": "2023-09-03T15:00:43.639Z", "effective_severity_level": "critical", "ignored": false, "key": "SNYK-JS-TECHDOCSCLIEMBEDDEDAPP-5868849", "problems": [ { "id": "SNYK-JS-TECHDOCSCLIEMBEDDEDAPP-5868849", "source": "SNYK", "type": "vulnerability", "updated_at": "2025-04-10T06:43:02.418711Z" } ], "resolution": { "details": "", "resolved_at": "2023-09-19T03:19:14.456Z", "type": "disappeared" }, "status": "resolved", "title": "Malicious Package", "type": "package_vulnerability", "updated_at": "2023-09-19T03:19:14.456Z" }, "relationships": { "organization": { "data": { "id": "a37b1727-9dc0-4f2c-9b59-7c5908e4d4d2", "type": "organization" }, "links": { "related": "/orgs/a37b1727-9dc0-4f2c-9b59-7c5908e4d4d2" } }, "scan_item": { "data": { "id": "049630b0-d8id-4e8c-97f8-1daf396642fb", "type": "project" }, "links": { "related": "/orgs/a37b1727-9dc0-4f2c-9b59-7c5908e4d4d2/projects/049630b0-c683-4e8c-97f8-1daf396642fb" } } }, "__organization": { "id": "a37b1727-9dc0-4f2c-9b59-7c5908e4", "type": "org", "attributes": { "group_id": "c7ec7fa4-50ae-450f-8b48-49270cfc", "is_personal": false, "name": "NFR - Shared", "slug": "nfr-shared", "created_at": "2023-08-14T18:34:58Z", "updated_at": "2025-08-22T10:09:27Z" }, "relationships": { "member_role": { "data": { "id": "d8ba290b-de7a-4957-9f0b-9409d7b4", "type": "org_role" } } }, "group_id": "c7ec7fa4-10dx-450f-8b48-49270cfc54b5", "is_personal": false, "name": "NFR - Shared", "slug": "nfr-shared", "created_at": "2023-08-14T18:34:58Z", "updated_at": "2025-08-22T10:09:27Z" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | Snyk issue API query params, for more information please look at the [API docs](https://docs.snyk.io/snyk-api/reference/issues#get-orgs-org_id-issues) | | `projectQueryParams` | `object` | Filters the projects whose issues are ingested. When omitted, issues from all projects are returned. | | `enrichWithProject` | `boolean` | Enrich each vulnerability with its associated project data. For large orgs this is API and memory intensive — use projectQueryParams to limit scope. Default: `false`. | | `attachIgnoreData` | `boolean` | Attach the matching ignore policy to each vulnerability under `__ignore_data`. Requires a per-project lookup, so use projectQueryParams to limit scope on large orgs. This uses Snyk's v1 ignore API which may become deprecated in the future. Default: `false`. | `apiQueryParams` parameters | Parameter | Type | Description | | -------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `type` | `string` | The type of an issue. Allowed values: `package_vulnerability`, `license`, `cloud`, `code`, `custom`, `config`. | | `updated_before` | `string` | Return issues last updated before this date. Must be RFC3339 with timezone (e.g. 2021-05-29T09:50:54Z). | | `updated_after` | `string` | Return issues last updated after this date. Must be RFC3339 with timezone (e.g. 2021-05-29T09:50:54Z). | | `created_before` | `string` | Return issues created before this date. Must be RFC3339 with timezone (e.g. 2021-05-29T09:50:54Z). | | `created_after` | `string` | Return issues created after this date. Must be RFC3339 with timezone (e.g. 2021-05-29T09:50:54Z). | | `effective_severity_level` | `array` | A filter to select issues with the provided effective severity level. Allowed values: `info`, `low`, `medium`, `high`, `critical`. | | `status` | `array` | A filter to select issues with the provided status. Allowed values: `open`, `resolved`. | | `ignored` | `boolean` | When true, return only ignored issues. When false, return only non-ignored issues. Omit to return all. | `projectQueryParams` parameters | Parameter | Type | Description | | ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `target_id` | `array` | Only return projects in the provided targets | | `target_reference` | `string` | Return projects that match the provided target reference | | `target_file` | `string` | Return projects that match the provided target file | | `target_runtime` | `string` | Return projects that match the provided target runtime | | `names` | `array` | Return projects that match the provided names | | `names_start_with` | `array` | Return projects with names starting with the specified prefix. | | `origins` | `array` | Return projects that match the provided origins. | | `types` | `array` | Return projects of the provided type (e.g. npm, pip, docker) | | `tags` | `array` | Return projects that match all the provided tags (key=value format). Tag values must not contain commas. | | `business_criticality` | `array` | Return projects with the provided business criticality Allowed values: `critical`, `high`, `medium`, `low`. | | `environment` | `array` | Return projects with the provided environment Allowed values: `frontend`, `backend`, `internal`, `external`, `mobile`, `saas`, `onprem`, `hosted`, `distributed`. | | `lifecycle` | `array` | Return projects with the provided lifecycle Allowed values: `production`, `development`, `sandbox`. | | `ids` | `array` | Return only projects with the provided IDs | Default mappingYAML ``` - kind: policy selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"snykOrgPolicies"' properties: expires: .attributes.action.data.expires ignore_type: .attributes.action.data.ignore_type reason: .attributes.action.data.reason action_type: .attributes.action_type review: .attributes.review ``` Support Org-level Policy APIs are only available for use with Snyk Code Consistent Ignores. To enable this feature, see [Snyk's Consistent Ignores documentation](https://docs.snyk.io/manage-risk/prioritize-issues-for-fixing/ignore-issues/consistent-ignores-for-snyk-code#enable-snyk-code-consistent-ignores). Sample payloadJSON ``` { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "type": "policy", "attributes": { "action": { "data": { "expires": "2026-04-21T23:00:00Z", "ignore_type": "temporary-ignore", "reason": "Ignore temporary for testing purposes" } }, "action_type": "ignore", "conditions_group": { "conditions": [ { "field": "snyk/asset/finding/v1", "operator": "includes", "value": "b2c3d4e5-f6a7-8901-bcde-f12345678901" } ], "logical_operator": "and" }, "created_at": "2026-04-20T14:28:59.890359Z", "created_by": { "email": "john.doe@example.com", "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "name": "John Doe" }, "name": "Ignore Issue b2c3d4e5-f6a7-8901-bcde-f12345678901", "review": "not-required", "updated_at": "2026-04-20T14:28:59.890359Z" }, "__organization": { "id": "d4e5f6a7-b8c9-0123-defa-234567890123", "type": "org", "attributes": { "group_id": "e5f6a7b8-c9d0-1234-efab-345678901234", "is_personal": false, "name": "Port NFR - Shared", "slug": "port-nfr-shared", "created_at": "2023-08-14T18:34:58Z", "updated_at": "2026-04-20T14:12:31Z" }, "relationships": { "member_role": { "data": { "id": "f6a7b8c9-d0e1-2345-fabc-456789012345", "type": "org_role" } } }, "group_id": "e5f6a7b8-c9d0-1234-efab-345678901234", "is_personal": false, "name": "Port NFR - Shared", "slug": "port-nfr-shared", "created_at": "2023-08-14T18:34:58Z", "updated_at": "2026-04-20T14:12:31Z" } } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Snyk 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 Snyk 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Snyk is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Snyk can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) --- # SonarQube ![SonarQube icon](/img/guides/icons/SonarQube.svg)![SonarQube icon](/img/guides/icons/SonarQube.svg) Loading version... Port's SonarQube integration allows you to model SonarQube resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://next.sonarqube.com/sonarqube/web_api/api/projects/search)[Issue](https://next.sonarqube.com/sonarqube/web_api/api/issues)[Saas Analysis](https://docs.sonarsource.com/sonarqube-cloud/advanced-setup/web-api)[On-Premises Analysis](https://docs.sonarsource.com/sonarqube-server/extension-guide/web-api)[Projects (GA)](https://next.sonarqube.com/sonarqube/web_api/api/projects/search)[Analysis](https://next.sonarqube.com/sonarqube/web_api/api/project_analyses/search)[Portfolio](https://next.sonarqube.com/sonarqube/web_api/api/views) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: projects_ga selector: query: 'true' apiFilters: qualifier: - TRK metrics: - code_smells - coverage - bugs - vulnerabilities - duplicated_files - security_hotspots - new_violations - new_coverage - new_duplicated_lines_density port: entity: mappings: identifier: .key title: .name blueprint: '"sonarQubeProject"' properties: organization: .organization link: .__link qualityGateStatus: .__branch.status.qualityGateStatus lastAnalysisDate: .analysisDate numberOfBugs: .__measures[]? | select(.metric == "bugs") | .value numberOfCodeSmells: .__measures[]? | select(.metric == "code_smells") | .value numberOfVulnerabilities: .__measures[]? | select(.metric == "vulnerabilities") | .value numberOfHotSpots: .__measures[]? | select(.metric == "security_hotspots") | .value numberOfDuplications: .__measures[]? | select(.metric == "duplicated_files") | .value coverage: .__measures[]? | select(.metric == "coverage") | .value mainBranch: .__branch.name revision: .revision managed: .managed relations: group: '"all_teams"' - kind: issues selector: query: 'true' apiFilters: resolved: 'false' projectApiFilters: {} port: entity: mappings: identifier: .key title: .message blueprint: '"sonarQubeIssue"' properties: type: .type severity: .severity link: .__link status: .status assignees: .assignee tags: .tags createdAt: .creationDate relations: sonarQubeProject: .project ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/examples/.md). ProjectIssueSaas AnalysisOn-Premise AnalysisPortfolio Default mappingYAML ``` - kind: projects_ga selector: query: 'true' apiFilters: qualifier: - TRK metrics: - code_smells - coverage - bugs - vulnerabilities - duplicated_files - security_hotspots - new_violations - new_coverage - new_duplicated_lines_density port: entity: mappings: identifier: .key title: .name blueprint: '"sonarQubeProject"' properties: organization: .organization link: .__link qualityGateStatus: .__branch.status.qualityGateStatus lastAnalysisDate: .analysisDate numberOfBugs: .__measures[]? | select(.metric == "bugs") | .value numberOfCodeSmells: .__measures[]? | select(.metric == "code_smells") | .value numberOfVulnerabilities: .__measures[]? | select(.metric == "vulnerabilities") | .value numberOfHotSpots: .__measures[]? | select(.metric == "security_hotspots") | .value numberOfDuplications: .__measures[]? | select(.metric == "duplicated_files") | .value coverage: .__measures[]? | select(.metric == "coverage") | .value mainBranch: .__branch.name revision: .revision managed: .managed relations: group: '"all_teams"' ``` Sample payloadJSON ``` { "organization": "peygis", "key": "PeyGis_Chatbot_For_Social_Media_Transaction", "name": "Chatbot_For_Social_Media_Transaction", "isFavorite": true, "visibility": "public", "eligibilityStatus": "COMPLETED", "eligible": true, "isNew": false, "lastAnalysisDate": "2017-03-02T15:21:47+0300", "revision": "7be96a94ac0c95a61ee6ee0ef9c6f808d386a355", "managed": false, "__measures": [ { "metric": "bugs", "value": "6", "bestValue": false }, { "metric": "code_smells", "value": "216", "bestValue": false }, { "metric": "duplicated_files", "value": "2", "bestValue": false }, { "metric": "vulnerabilities", "value": "1", "bestValue": false }, { "metric": "security_hotspots", "value": "8", "bestValue": false } ], "__branch": { "name": "master", "isMain": true, "type": "LONG", "status": { "qualityGateStatus": "ERROR", "bugs": 6, "vulnerabilities": 1, "codeSmells": 216 }, "analysisDate": "2023-09-07T14:38:41+0200", "commit": { "sha": "5b01b6dcb200df0bfd1c66df65be30f9ea5423d8", "author": { "name": "Username", "login": "Username@github", "avatar": "9df2ac1caa70b0a67ff0561f7d0363e5" }, "date": "2023-09-07T14:38:36+0200", "message": "Merge pull request #21 from PeyGis/test-sonar" } }, "__link": "https://sonarcloud.io/project/overview?id=PeyGis_Chatbot_For_Social_Media_Transaction" } ``` Port entityJSON ``` { "identifier": "PeyGis_Chatbot_For_Social_Media_Transaction", "title": "Chatbot_For_Social_Media_Transaction", "blueprint": "sonarQubeProject", "team": [], "properties": { "organization": "peygis", "link": "https://sonarcloud.io/project/overview?id=PeyGis_Chatbot_For_Social_Media_Transaction", "lastAnalysisDate": "2023-09-07T12:38:41.000Z", "numberOfBugs": 6, "numberOfCodeSmells": 216, "numberOfVulnerabilities": 1, "numberOfHotSpots": 8, "numberOfDuplications": 2, "mainBranch": "master", "mainBranchLastAnalysisDate": "2023-09-07T12:38:41.000Z", "revision": "7be96a94ac0c95a61ee6ee0ef9c6f808d386a355", "managed": true }, "relations": {}, "icon": "sonarqube" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiFilters` | `object` | Query parameters to filter the projects to retrieve | | `metrics` | `array` | List of metrics to retrieve Default: `["code_smells","coverage","bugs","vulnerabilities","duplicated_files","security_hotspots","new_violations","new_coverage","new_duplicated_lines_density"]`. | `apiFilters` parameters | Parameter | Type | Description | | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `filter` | `object` | Criteria used to narrow which SonarQube components are retrieved, including name substring, metric thresholds, Quality Gate status, languages, and tags | Default mappingYAML ``` - kind: issues selector: query: 'true' apiFilters: resolved: 'false' projectApiFilters: {} port: entity: mappings: identifier: .key title: .message blueprint: '"sonarQubeIssue"' properties: type: .type severity: .severity link: .__link status: .status assignees: .assignee tags: .tags createdAt: .creationDate relations: sonarQubeProject: .project ``` Sample payloadJSON ``` { "key": "AYhnRlhI0rLhE5EBPGHW", "rule": "xml:S1135", "severity": "INFO", "component": "PeyGis_Chatbot_For_Social_Media_Transaction:node_modules/json-schema/draft-zyp-json-schema-04.xml", "project": "PeyGis_Chatbot_For_Social_Media_Transaction", "line": 313, "hash": "8346d5371c3d1b0d1d57937c7b967090", "textRange": { "startLine": 313, "endLine": 313, "startOffset": 3, "endOffset": 56 }, "flows": [], "status": "OPEN", "message": "Complete the task associated to this \"TODO\" comment.", "effort": "0min", "debt": "0min", "assignee": "Username@github", "author": "email@gmail.com", "tags": [], "creationDate": "2018-04-06T02:44:46+0200", "updateDate": "2023-05-29T13:30:14+0200", "type": "CODE_SMELL", "organization": "peygis", "cleanCodeAttribute": "COMPLETE", "cleanCodeAttributeCategory": "INTENTIONAL", "impacts": [ { "softwareQuality": "MAINTAINABILITY", "severity": "LOW" } ], "__link": "https://sonarcloud.io/project/issues?open=AYhnRlhI0rLhE5EBPGHW&id=PeyGis_Chatbot_For_Social_Media_Transaction" } ``` Port entityJSON ``` { "identifier": "AYhnRlhI0rLhE5EBPGHW", "title": "Complete the task associated to this \"TODO\" comment.", "blueprint": "sonarQubeIssue", "team": [], "properties": { "type": "CODE_SMELL", "severity": "INFO", "link": "https://sonarcloud.io/project/issues?open=AYhnRlhI0rLhE5EBPGHW&id=PeyGis_Chatbot_For_Social_Media_Transaction", "status": "OPEN", "assignees": "Username@github", "tags": [], "createdAt": "2018-04-06T00:44:46.000Z" }, "relations": { "sonarQubeProject": "PeyGis_Chatbot_For_Social_Media_Transaction" }, "icon": "sonarqube" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiFilters` | `object` | Query parameters to filter the issues to retrieve. For example, you can use the 'assigned' parameter to retrieve only assigned or unassigned issues. | | `projectApiFilters` | `object` | Allows users to control which projects to query the issues for | `apiFilters` parameters | Parameter | Type | Description | | ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `assigned` | `string` | To retrieve assigned or unassigned issues Allowed values: `yes`, `no`, `true`, `false`. | | `assignees` | `array` | List of assignee login names to filter by | | `cleanCodeAttributeCategories` | `array` | Include only issues belonging to one of the specified clean code attribute categories Allowed values: `ADAPTABLE`, `CONSISTENT`, `INTENTIONAL`, `RESPONSIBLE`. | | `codeVariants` | `array` | Include only issues affecting one of the specified code variants (e.g. \['variant1', 'variant2']) | | `createdBefore` | `string` | To retrieve issues created before the given date | | `createdAfter` | `string` | To retrieve issues created after the given date | | `cwe` | `array` | Include only issues mapped to one of the specified CWE identifiers (e.g. \['89', '352']) | | `impactSeverities` | `array` | Filter issues by their impact severity Allowed values: `HIGH`, `LOW`, `MEDIUM`. | | `impactSoftwareQualities` | `array` | Filter issues by the software quality dimension they affect Allowed values: `MAINTAINABILITY`, `RELIABILITY`, `SECURITY`. | | `statuses` | `array` | Filter issues by their status Allowed values: `OPEN`, `CONFIRMED`, `FALSE_POSITIVE`, `ACCEPTED`, `FIXED`. | | `languages` | `array` | Include only issues in files written in one of the specified languages (e.g. \['java', 'python']) | | `owaspAsvsLevel` | `string` | Filter issues by OWASP Application Security Verification Standard (ASVS) level Allowed values: `1`, `2`, `3`. | | `resolved` | `string` | To retrieve resolved or unresolved issues Allowed values: `yes`, `no`, `true`, `false`. | | `rules` | `array` | Include only issues raised by one of the specified coding rule keys (e.g. \['java:S1234', 'python:S5678']) | | `scopes` | `array` | Filter by issue scope: MAIN for source code, TESTS for test code Allowed values: `MAIN`, `TESTS`. | | `sonarsourceSecurity` | `array` | Include only issues mapped to one of the specified SonarSource security categories (e.g. \['sql-injection', 'xss']) | | `tags` | `array` | Include only issues that have at least one of the specified tags (e.g. \['security', 'performance']) | `projectApiFilters` parameters | Parameter | Type | Description | | ------------------- | --------- | ------------------------------------------------------------------ | | `analyzedBefore` | `string` | Retrieve only projects last analyzed before this date | | `onProvisionedOnly` | `boolean` | Retrieve only provisioned projects that have not yet been analyzed | | `projects` | `array` | List of project keys to include | Default mappingYAML ``` - kind: saas_analysis selector: query: "true" port: entity: mappings: blueprint: '"sonarQubeAnalysis"' identifier: .analysisId title: .__commit.message // .analysisId properties: branch: .__branchName fixedIssues: .measures.violations_fixed newIssues: .measures.violations_added coverage: .measures.coverage_change duplications: .measures.duplicated_lines_density_change createdAt: .__analysisDate relations: sonarQubeProject: .__project ``` Sample payloadJSON ``` { "analysisId": "AYpvptJNv89mE9ClYP-q", "firstAnalysis": false, "measures": { "violations_added": "0", "violations_fixed": "0", "coverage_change": "0.0", "duplicated_lines_density_change": "0.0", "ncloc_change": "0" }, "branch": { "analysisDate": "2023-09-07T12:38:41.279Z", "isMain": true, "name": "master", "commit": { "sha": "5b01b6dcb200df0bfd1c66df65be30f9ea5423d8", "author": { "avatar": "9df2ac1caa70b0a67ff0561f7d0363e5", "login": "Username@github", "name": "Username" }, "date": "2023-09-07T12:38:36Z", "message": "Merge pull request #21 from PeyGis/test-sonar" }, "type": "LONG", "status": { "qualityGateStatus": "ERROR" } }, "__branchName": "master", "__analysisDate": "2023-09-07T12:38:41.279Z", "__commit": { "sha": "5b01b6dcb200df0bfd1c66df65be30f9ea5423d8", "author": { "avatar": "9df2ac1caa70b0a67ff0561f7d0363e5", "login": "Username@github", "name": "Username" }, "date": "2023-09-07T12:38:36Z", "message": "Merge pull request #21 from PeyGis/test-sonar" }, "__project": "PeyGis_Chatbot_For_Social_Media_Transaction" } ``` Port entityJSON ``` { "identifier": "AYpvptJNv89mE9ClYP-q", "title": "Merge pull request #21 from PeyGis/test-sonar", "blueprint": "sonarQubeAnalysis", "team": [], "properties": { "branch": "master", "fixedIssues": 0, "newIssues": 0, "coverage": 0, "duplications": 0, "createdAt": "2023-09-07T12:38:41.279Z" }, "relations": { "sonarQubeProject": "PeyGis_Chatbot_For_Social_Media_Transaction" }, "icon": "sonarqube" } ``` BlueprintJSON Create in Port ``` { "identifier": "sonarQubeAnalysis", "title": "SonarQube Analysis", "icon": "sonarqube", "schema": { "properties": { "branch": { "type": "string", "title": "Branch", "icon": "GitVersion" }, "fixedIssues": { "type": "number", "title": "Fixed Issues" }, "newIssues": { "type": "number", "title": "New Issues" }, "coverage": { "title": "Coverage", "type": "number" }, "duplications": { "type": "number", "title": "Duplications" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } } }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "sonarQubeProject": { "target": "sonarQubeProject", "required": false, "title": "SonarQube Project", "many": false } } } ``` Mapping configurationYAML ``` - kind: onprem_analysis selector: query: 'true' port: entity: mappings: blueprint: '"sonarQubeAnalysis"' identifier: .__project + "-" + .key title: .title properties: branch: .branch newIssues: .__measures[]? | select(.metric == "new_violations") | .period.value coverage: .__measures[]? | select(.metric == "new_coverage") | .period.value duplications: .__measures[]? | select(.metric == "new_duplicated_lines_density") | .period.value createdAt: .analysisDate relations: sonarQubeProject: .__project ``` Default mappingYAML ``` - kind: portfolios selector: query: 'true' port: entity: mappings: identifier: .key title: .name blueprint: '"sonarQubePortfolio"' properties: description: .description visibility: if .visibility then .visibility | ascii_upcase else null end selectionMode: if .selectionMode then .selectionMode | ascii_upcase else null end disabled: .disabled relations: subPortfolios: .subViews | map(select((.qualifier | IN("VW", "SVW"))) | .key) referencedBy: .referencedBy | map(select((.qualifier | IN("VW", "SVW"))) | .key) ``` Sample payloadJSON ``` { "key": "GetPort_SelfService", "name": "GetPort SelfService", "desc": "Test", "qualifier": "VW", "visibility": "public", "selectionMode": "NONE", "subViews": [ { "key": "GetPort_SelfService_Second", "name": "GetPort SelfService Second", "qualifier": "SVW", "selectionMode": "NONE", "subViews": [ { "key": "GetPort_SelfService_Third", "name": "GetPort SelfService Third", "qualifier": "SVW", "selectionMode": "NONE", "subViews": [], "referencedBy": [] }, { "key": "Port_Test", "name": "Port Test", "qualifier": "SVW", "selectionMode": "NONE", "subViews": [], "referencedBy": [] } ], "referencedBy": [] }, { "key": "Python", "name": "Python", "qualifier": "SVW", "selectionMode": "NONE", "subViews": [ { "key": "Time", "name": "Time", "qualifier": "SVW", "selectionMode": "NONE", "subViews": [ { "key": "port_ayodeji", "name": "port-ayodeji", "qualifier": "SVW", "selectionMode": "NONE", "subViews": [ { "key": "port_ayodeji:REferenced", "name": "REferenced", "qualifier": "VW", "visibility": "public", "originalKey": "REferenced" } ], "referencedBy": [] } ], "referencedBy": [] } ], "referencedBy": [] }, { "key": "GetPort_SelfService:Authentication_Application", "name": "Authentication Application", "desc": "For auth services", "qualifier": "APP", "visibility": "private", "selectedBranches": [ "main" ], "originalKey": "Authentication_Application" } ], "referencedBy": [ { "key": "GetPort_SelfService:Authentication_Application", "name": "Authentication Application", "desc": "For auth services", "qualifier": "VW", "visibility": "private", "selectedBranches": [ "main" ], "originalKey": "Authentication_Application" } ] } ``` Port entityJSON ``` { "identifier": "GetPort_SelfService", "title": "GetPort SelfService", "blueprint": "sonarQubePortfolio", "properties": { "description": null, "visibility": "PUBLIC", "selectionMode": "NONE", "disabled": null }, "relations": { "subPortfolios": [ "GetPort_SelfService_Second", "Python" ], "referencedBy": [ "GetPort_SelfService:Authentication_Application" ] } } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 SonarQube 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 SonarQube 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If SonarQube is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that SonarQube can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found on the sonarqube [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/examples/.md) --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ## Project[​](#project "Direct link to Project") **Projects blueprint (click to expand)** Create in Port ``` { "identifier": "sonarQubeProject", "title": "SonarQube Project", "icon": "sonarqube", "schema": { "properties": { "organization": { "type": "string", "title": "Organization", "icon": "TwoUsers" }, "link": { "type": "string", "format": "url", "title": "Link", "icon": "Link" }, "lastAnalysisDate": { "type": "string", "format": "date-time", "icon": "Clock", "title": "Last Analysis Date" }, "qualityGateStatus": { "title": "Quality Gate Status", "type": "string", "enum": [ "OK", "WARN", "ERROR" ], "enumColors": { "OK": "green", "WARN": "yellow", "ERROR": "red" } }, "numberOfBugs": { "type": "number", "title": "Number Of Bugs" }, "numberOfCodeSmells": { "type": "number", "title": "Number Of CodeSmells" }, "numberOfVulnerabilities": { "type": "number", "title": "Number Of Vulnerabilities" }, "numberOfHotSpots": { "type": "number", "title": "Number Of HotSpots" }, "numberOfDuplications": { "type": "number", "title": "Number Of Duplications" }, "coverage": { "type": "number", "title": "Coverage" }, "mainBranch": { "type": "string", "icon": "Git", "title": "Main Branch" }, "mainBranchLastAnalysisDate": { "type": "string", "format": "date-time", "icon": "Clock", "title": "Main Branch Last Analysis Date" }, "revision": { "type": "string", "title": "Revision" }, "managed": { "type": "boolean", "title": "Managed" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": { "criticalOpenIssues": { "title": "Number Of Open Critical Issues", "type": "number", "target": "sonarQubeIssue", "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "in", "value": ["OPEN", "REOPENED"] }, { "property": "severity", "operator": "=", "value": "CRITICAL" } ] }, "calculationSpec": { "calculationBy": "entities", "func": "count" } }, "numberOfOpenIssues": { "title": "Number Of Open Issues", "type": "number", "target": "sonarQubeIssue", "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "in", "value": [ "OPEN", "REOPENED" ] } ] }, "calculationSpec": { "calculationBy": "entities", "func": "count" } } }, "relations": {} } ``` **Integration configuration (click to expand)** Define your own metrics Besides filtering the API data, the integration provides a mechanism to allow users to define their own list of metrics used in SonarQube to evaluate the code. This list can be defined in the `selector.metrics` property. A complete list of valid SonarQube metrics can be in the [SonarQube documentation](https://docs.sonarsource.com/sonarqube/latest/user-guide/code-metrics/metrics-definition/). ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: projects_ga selector: query: 'true' apiFilters: qualifier: - TRK metrics: - code_smells - coverage - bugs - vulnerabilities - duplicated_files - security_hotspots - new_violations - new_coverage - new_duplicated_lines_density port: entity: mappings: blueprint: '"sonarQubeProject"' identifier: .key title: .name properties: organization: .organization link: .__link qualityGateStatus: .__branch.status.qualityGateStatus lastAnalysisDate: (.lastAnalysisDate // null) | if . then sub("(?[+-]\\d{2})(?\\d{2})$"; "\(.h):\(.m)") else null end numberOfBugs: .__measures[]? | select(.metric == "bugs") | .value numberOfCodeSmells: .__measures[]? | select(.metric == "code_smells") | .value numberOfVulnerabilities: .__measures[]? | select(.metric == "vulnerabilities") | .value numberOfHotSpots: .__measures[]? | select(.metric == "security_hotspots") | .value numberOfDuplications: .__measures[]? | select(.metric == "duplicated_files") | .value coverage: .__measures[]? | select(.metric == "coverage") | .value mainBranch: .__branch.name mainBranchLastAnalysisDate: (.__branch.analysisDate // null) | if . then sub("(?[+-]\\d{2})(?\\d{2})$"; "\(.h):\(.m)") else null end revision: .revision managed: .managed ``` ## Issue[​](#issue "Direct link to Issue") **Issue blueprint (click to expand)** Create in Port ``` { "identifier": "sonarQubeIssue", "title": "SonarQube Issue", "icon": "sonarqube", "schema": { "properties": { "type": { "type": "string", "title": "Type", "enum": ["CODE_SMELL", "BUG", "VULNERABILITY"] }, "severity": { "type": "string", "title": "Severity", "enum": ["MAJOR", "INFO", "MINOR", "CRITICAL", "BLOCKER"], "enumColors": { "MAJOR": "orange", "INFO": "green", "CRITICAL": "red", "BLOCKER": "red", "MINOR": "yellow" } }, "link": { "type": "string", "format": "url", "icon": "Link", "title": "Link" }, "status": { "type": "string", "title": "Status", "enum": ["OPEN", "CLOSED", "RESOLVED", "REOPENED", "CONFIRMED"] }, "assignees": { "title": "Assignees", "type": "string", "icon": "TwoUsers" }, "tags": { "type": "array", "title": "Tags" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } } }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "sonarQubeProject": { "target": "sonarQubeProject", "required": false, "title": "SonarQube Project", "many": false } } } ``` **Integration configuration (click to expand)** filter issues The integration provides an option to filter the data that is retrieved from the SonarQube API using the following attributes: 1. `assigned`: To retrieve assigned or unassigned issues. Accepts values: `yes`, `no`, `true`, `false` 2. `assignees`: A list of assignee logins 3. `cleanCodeAttributeCategories`: List of clean code attribute categories. Accepts values: `ADAPTABLE`, `CONSISTENT`, `INTENTIONAL`, `RESPONSIBLE` 4. `createdBefore`: To retrieve issues created before the given date 5. `createdAfter`: To retrieve issues created after the given date 6. `impactSeverities`: List of impact severities. Accepts values: `HIGH`, `LOW`, `MEDIUM` 7. `impactSoftwareQualities`: List of impact software qualities. Accepts values: `MAINTAINABILITY`, `RELIABILITY`, `SECURITY` 8. `statuses`: List of statuses. Accepts values: `OPEN`, `CONFIRMED`, `FALSE_POSITIVE`, `ACCEPTED`, `FIXED` 9. `languages`: List of languages 10. `resolved`: To retrieve resolved or unresolved issues. Accepts values: `yes`, `no`, `true`, `false` 11. `scopes`: List of scopes. Accepts values: `MAIN`, `TESTS` 12. `tags`: List of tags These attributes can be enabled using the path: `selector.apiFilters`. By default, the integration fetches unresolved SonarQube issues. It is also possible to configure the integration to fetch issues from a SonarQube project using the path: `selector.projectApiFilters.filter` while specifying any of project attributes listed above. ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: issues selector: query: "true" apiFilters: resolved: 'false' projectApiFilters: filter: qualifier: TRK port: entity: mappings: blueprint: '"sonarQubeIssue"' identifier: .key title: .message properties: type: .type severity: .severity link: .__link status: .status assignees: .assignee tags: .tags createdAt: .creationDate relations: sonarQubeProject: .project ``` ## Saas Analysis[​](#saas-analysis "Direct link to Saas Analysis") **Saas analysis blueprint (click to expand)** Create in Port ``` { "identifier": "sonarQubeAnalysis", "title": "SonarQube Analysis", "icon": "sonarqube", "schema": { "properties": { "branch": { "type": "string", "title": "Branch", "icon": "GitVersion" }, "fixedIssues": { "type": "number", "title": "Fixed Issues" }, "newIssues": { "type": "number", "title": "New Issues" }, "coverage": { "title": "Coverage", "type": "number" }, "duplications": { "type": "number", "title": "Duplications" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } } }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "sonarQubeProject": { "target": "sonarQubeProject", "required": false, "title": "SonarQube Project", "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: saas_analysis selector: query: "true" port: entity: mappings: blueprint: '"sonarQubeAnalysis"' identifier: .analysisId title: .__commit.message // .analysisId properties: branch: .__branchName fixedIssues: .measures.violations_fixed newIssues: .measures.violations_added coverage: .measures.coverage_change duplications: .measures.duplicated_lines_density_change createdAt: .__analysisDate relations: sonarQubeProject: .__project ``` ## On-Premise Analysis[​](#on-premise-analysis "Direct link to On-Premise Analysis") **On-premise analysis blueprint (click to expand)** Create in Port ``` { "identifier": "sonarQubeAnalysis", "title": "SonarQube Analysis", "icon": "sonarqube", "schema": { "properties": { "branch": { "type": "string", "title": "Branch", "icon": "GitVersion" }, "fixedIssues": { "type": "number", "title": "Fixed Issues" }, "newIssues": { "type": "number", "title": "New Issues" }, "coverage": { "title": "Coverage", "type": "number" }, "duplications": { "type": "number", "title": "Duplications" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } } }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "sonarQubeProject": { "target": "sonarQubeProject", "required": false, "title": "SonarQube Project", "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: onprem_analysis selector: query: 'true' port: entity: mappings: blueprint: '"sonarQubeAnalysis"' identifier: .__project + "-" + .key title: .title properties: branch: .branch newIssues: .__measures[]? | select(.metric == "new_violations") | .period.value coverage: .__measures[]? | select(.metric == "new_coverage") | .period.value duplications: .__measures[]? | select(.metric == "new_duplicated_lines_density") | .period.value createdAt: .analysisDate relations: sonarQubeProject: .__project ``` ## Portfolio[​](#portfolio "Direct link to Portfolio") **Portfolio blueprint (click to expand)** Create in Port ``` { "identifier": "sonarQubePortfolio", "title": "SonarQube Portfolio", "icon": "sonarqube", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "visibility": { "type": "string", "title": "Visibility", "enum": [ "PUBLIC", "PRIVATE" ], "enumColors": { "PUBLIC": "green", "PRIVATE": "lightGray" } }, "selectionMode": { "type": "string", "title": "Selection Mode", "enum": [ "AUTO", "MANUAL", "NONE" ], "enumColors": { "AUTO": "blue", "MANUAL": "green", "NONE": "lightGray" } }, "disabled": { "type": "boolean", "title": "Disabled" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "referencedBy": { "title": "Referenced By", "target": "sonarQubePortfolio", "required": false, "many": true }, "subPortfolios": { "title": "Sub Portfolios", "target": "sonarQubePortfolio", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true resources: - kind: portfolios selector: query: 'true' port: entity: mappings: identifier: .key title: .name blueprint: '"sonarQubePortfolio"' properties: description: .description visibility: if .visibility then .visibility | ascii_upcase else null end selectionMode: if .selectionMode then .selectionMode | ascii_upcase else null end disabled: .disabled relations: subPortfolios: .subViews | map(select((.qualifier | IN("VW", "SVW"))) | .key) referencedBy: .referencedBy | map(select((.qualifier | IN("VW", "SVW"))) | .key) ``` --- # Wiz ![Wiz icon](/img/guides/icons/Wiz.svg)![Wiz icon](/img/guides/icons/Wiz.svg) Loading version... Port's Wiz integration allows you to model Wiz resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://docs.wiz.io/)[Issue](https://docs.wiz.io/)[Control](https://docs.wiz.io/)[Service ticket](https://docs.wiz.io/)[Vulnerability](https://docs.wiz.io/)[SBOM artifact](https://docs.wiz.io/)[Repository](https://docs.wiz.io/)[Technology](https://docs.wiz.io/)[Hosted technology](https://docs.wiz.io/) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### Wiz Credentials[​](#wiz-credentials "Direct link to Wiz Credentials") You will need the following connection details to configure Wiz: * Wiz API URL (API Endpoint URL) * Wiz Token URL
There are two possible endpoints depending on your service account's identity provider: * Amazon Cognito: * Auth0: Learn more [here](https://win.wiz.io/reference/quickstart#generate-a-bearer-token-and-start-using-wiz-api). * Client ID and Client Secret 1. **Find Your Wiz API URL**: * Login to Wiz account. * Click the **User Profile** icon available at the top right of the screen and click the **User Settings** option. * Click the **Tenant** option from the left options menu. * The system displays the **API Endpoint URL**. * Copy and save the **API URL** to use while configuring the Wiz integration. ![Wiz tenant settings API Endpoint URL](/img/build-your-software-catalog/sync-data-to-catalog/code-quality-security/wizApiUrl.png) 2. **Get the Client ID and Client Secret** You must create a service account in Wiz to generate the Client ID and Client Secret. Follow the below steps to get the Client ID and Client Secret: * Login to **Wiz with the Project Admin role**. * Click the **Settings** icon available at the top-right of the page. ![Wiz Settings Service Accounts list](/img/build-your-software-catalog/sync-data-to-catalog/code-quality-security/wizAddSvcAccount.png) * On the Settings page, Click **Service Accounts** from the left menu. * Create a Service Account: * Click **Add Service Account**. * Provide a descriptive **Service Account Name**. * **Type**: Select **Custom Integration (GraphQL API)**. * **Project**: Choose the relevant project(s). * **API Scopes**: Select only the `read:projects`, `read:issues`, `read:vulnerabilities`, `read:inventory` and `read:sbom_artifacts` permissions. * Click **Add Service Account** at the bottom of the page to save. * Retrieve Credentials: Wiz will display your Client ID and Client Secret. * Save Credentials: Copy and store them securely for use in Port. ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to 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: blueprint: '"wizProject"' identifier: .id title: .name properties: archived: .archived businessUnit: .businessUnit description: .description - kind: issue selector: query: 'true' port: entity: mappings: blueprint: '"wizIssue"' identifier: .id title: .entitySnapshot.name + " | " + .entitySnapshot.type properties: url: .id as $id | "https://app.wiz.io/issues#~(issue~'" + $id + ")" status: .status severity: .severity vulnerabilityType: .type notes: .notes wizIssueID: .entitySnapshot.id cloudResourceType: .entitySnapshot.type resourceName: .entitySnapshot.name cloudPlatform: .entitySnapshot.cloudPlatform linkToResource: if .entitySnapshot.cloudProviderURL == "" then null else .entitySnapshot.cloudProviderURL end cloudResourceID: .entitySnapshot.providerId cloudRegion: .entitySnapshot.region resourceGroupExternalId: .entitySnapshot.resourceGroupExternalId subscriptionExternalId: .entitySnapshot.subscriptionExternalId subscriptionName: .entitySnapshot.subscriptionName subscriptionTags: .entitySnapshot.subscriptionTags resourceTags: .entitySnapshot.tags vulnerability: .entitySnapshot createdAt: .createdAt updatedAt: .updatedAt statusChangedAt: .statusChangedAt resolvedAt: .resolvedAt relations: projects: .projects | map(.id) serviceTickets: .serviceTickets[].externalId control: .sourceRule.id - kind: control selector: query: 'true' port: entity: mappings: blueprint: '"wizControl"' identifier: .id title: .name properties: controlDescription: .controlDescription resolutionRecommendation: .resolutionRecommendation - kind: serviceTicket selector: query: 'true' port: entity: mappings: blueprint: '"wizServiceTicket"' identifier: .externalId title: .name properties: url: .url - kind: vulnerability-finding selector: query: 'true' port: entity: mappings: blueprint: '"wizVulnerabilityFinding"' identifier: .id title: .name properties: status: .status severity: .severity categories: .categories version: .version score: .score description: .description detailedName: .detailedName artifactMetadata: .artifactType remediation: .remediation detectionMethod: .detectionMethod environments: .environments link: .link portalUrl: .portalUrl origin: .origin vulnerabilityExternalId: .vulnerabilityExternalId CVEDescription: .CVEDescription hasFix: .hasFix hasExploit: .hasExploit isHighProfileThreat: .isHighProfileThreat updatedAt: .updatedAt resolvedAt: .resolvedAt firstDetectedAt: .firstDetectedAt publishedDate: .publishedDate rootComponent: .rootComponent applicationServices: .applicationServices relations: projects: .projects | map(.id) - kind: repository selector: query: 'true' port: entity: mappings: blueprint: '"wizRepository"' identifier: .id title: .name properties: url: .url platform: .platform public: .public archived: .archived visibility: .visibility organization: .organization.id branches: .branches | map(.id) relations: projects: .projects | map(.id) - kind: technology selector: query: 'true' port: entity: mappings: blueprint: '"wizTechnology"' identifier: .id title: .name properties: description: .description categories: .categories | map(.name) usage: .usage status: .status risk: .risk note: .note ownerName: .ownerName businessModel: .businessModel popularity: .popularity projectCount: .projectCount codeRepoCount: .codeRepoCount isCloudService: .isCloudService supportedOperatingSystems: .supportedOperatingSystems - kind: hosted-technology selector: query: 'true' port: entity: mappings: blueprint: '"wizHostedTechnology"' identifier: .id title: .name properties: detectionMethods: .detectionMethods installedPackages: .installedPackages firstSeen: .firstSeen updatedAt: .updatedAt cpe: .cpe relations: technology: .technology.id ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/examples/.md). ProjectIssueControlService TicketVulnerability FindingSBOM ArtifactRepositoryTechnologyHosted Technology Default mappingYAML ``` - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"wizProject"' identifier: .id title: .name properties: archived: .archived businessUnit: .businessUnit description: .description ``` Sample payloadJSON ``` { "id": "d6ac50bb-aec0-52fc-80ab-bacd7b02f178", "name": "Project1", "isFolder": false, "archived": false, "businessUnit": "Dev", "description": "Test project" } ``` Port entityJSON ``` { "identifier": "d6ac50bb-aec0-52fc-80ab-bacd7b02f178", "title": "Project1", "blueprint": "wizProject", "team": [], "icon": "NewRelic", "properties": { "archived": false, "businessUnit": "Dev", "description": "Test project" }, "createdAt": "2024-2-6T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-2-6T11:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `impact` | `string` | The business impact of the project. If empty, all projects are fetched. | | `includeArchived` | `boolean` | Include archived projects. False by default. | Default mappingYAML ``` - kind: issue selector: query: 'true' port: entity: mappings: blueprint: '"wizIssue"' identifier: .id title: .entitySnapshot.name + " | " + .entitySnapshot.type properties: url: .id as $id | "https://app.wiz.io/issues#~(issue~'" + $id + ")" status: .status severity: .severity vulnerabilityType: .type notes: .notes wizIssueID: .entitySnapshot.id cloudResourceType: .entitySnapshot.type resourceName: .entitySnapshot.name cloudPlatform: .entitySnapshot.cloudPlatform linkToResource: if .entitySnapshot.cloudProviderURL == "" then null else .entitySnapshot.cloudProviderURL end cloudResourceID: .entitySnapshot.providerId cloudRegion: .entitySnapshot.region resourceGroupExternalId: .entitySnapshot.resourceGroupExternalId subscriptionExternalId: .entitySnapshot.subscriptionExternalId subscriptionName: .entitySnapshot.subscriptionName subscriptionTags: .entitySnapshot.subscriptionTags resourceTags: .entitySnapshot.tags vulnerability: .entitySnapshot createdAt: .createdAt updatedAt: .updatedAt statusChangedAt: .statusChangedAt resolvedAt: .resolvedAt relations: projects: .projects | map(.id) serviceTickets: .serviceTickets[].externalId control: .sourceRule.id ``` Sample payloadJSON ``` { "id": "fffedba9-587f-4251-8c96-d966c183f10c", "sourceRule": { "__typename": "Control", "id": "9d7ef6e4-baed-47ba-99ec-a78a801f1e19", "name": "Publicly Exposed Assets with DataFindings ", "controlDescription": "", "resolutionRecommendation": "", "securitySubCategories": [ { "title": "Data Security", "category": { "name": "8 Data Security", "framework": { "name": "Wiz" } } } ] }, "createdAt": "2023-08-23T07:56:09.903743Z", "updatedAt": "2023-09-12T08:33:16.327851Z", "dueAt": null, "type": "TOXIC_COMBINATION", "resolvedAt": "2023-08-30T08:17:54.613564Z", "statusChangedAt": "2023-08-30T08:17:54.613564Z", "projects": [ { "id": "d6ac50bb-aec0-52fc-80ab-bacd7b02f178", "name": "Project1", "slug": "project1", "businessUnit": "Dev", "riskProfile": { "businessImpact": "MBI" } } ], "status": "RESOLVED", "severity": "HIGH", "entitySnapshot": { "id": "3d7dafdc-0087-55e0-81fd-a9e2b152fb47", "type": "DATA_FINDING", "nativeType": "", "name": "GDPR 2415", "status": null, "cloudPlatform": null, "cloudProviderURL": "", "providerId": "data##wizt-recEIECHXqlRPMZRw##wfke-jpb8-twwk-l7mm", "region": "", "resourceGroupExternalId": "", "subscriptionExternalId": "", "subscriptionName": "", "subscriptionTags": null, "tags": {}, "externalId": "data##wizt-recEIECHXqlRPMZRw##wfke-jpb8-twwk-l7mm" }, "serviceTickets": [], "notes": [ { "createdAt": "2023-09-12T08:33:16.29091Z", "updatedAt": "2023-09-12T08:33:16.366971Z", "text": "test", "user": null, "serviceAccount": { "name": "bot-ise" } }, { "createdAt": "2023-09-12T08:22:20.13926Z", "updatedAt": "2023-09-12T08:33:16.369728Z", "text": "test", "user": null, "serviceAccount": { "name": "bot-ise" } }, { "createdAt": "2023-09-12T08:21:49.663314Z", "updatedAt": "2023-09-12T08:33:16.371541Z", "text": "test", "user": null, "serviceAccount": { "name": "bot-ise" } } ] } ``` Port entityJSON ``` { "identifier": "fffedba9-587f-4251-8c96-d966c183f10c", "title": "GDPR 2415 | DATA_FINDING", "blueprint": "wizIssue", "icon": "Alert", "properties": { "url": "https://app.wiz.io/issues#~(issue~'fffedba9-587f-4251-8c96-d966c183f10c)", "status": "RESOLVED", "severity": "HIGH", "type": "TOXIC_COMBINATION", "notes": [], "vulnerability": { "id": "3d7dafdc-0087-55e0-81fd-a9e2b152fb47", "type": "DATA_FINDING", "nativeType": "", "name": "GDPR 2415", "status": null, "cloudPlatform": null, "cloudProviderURL": "", "providerId": "data##wizt-recEIECHXqlRPMZRw##wfke-jpb8-twwk-l7mm", "region": "", "resourceGroupExternalId": "", "subscriptionExternalId": "", "subscriptionName": "", "subscriptionTags": null, "tags": {}, "externalId": "data##wizt-recEIECHXqlRPMZRw##wfke-jpb8-twwk-l7mm" }, "createdAt": "2023-08-23T07:56:09.903743Z", "updatedAt": "2023-09-12T08:33:16.327851Z", "resolvedAt": "2023-08-30T08:17:54.613564Z", "statusChangedAt": "2023-08-30T08:17:54.613564Z" }, "relations": { "projects": ["d6ac50bb-aec0-52fc-80ab-bacd7b02f178"], "serviceTickets": [], "control": "9d7ef6e4-baed-47ba-99ec-a78a801f1e19" }, "createdAt": "2023-08-23T07:56:09.903743Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-09-12T08:33:16.327851Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `statusList` | `array` | List of statuses to filter issues by Default: `["OPEN","IN_PROGRESS"]`. | | `severityList` | `array` | List of severities to filter issues by. If empty, all severities are fetched. | | `typeList` | `array` | List of issue types to fetch. If empty, all issue types are fetched. | | `maxPages` | `integer` | Maximum number of pages to fetch for issues. Default: `500`. | Default mappingYAML ``` - kind: control selector: query: 'true' port: entity: mappings: blueprint: '"wizControl"' identifier: .id title: .name properties: controlDescription: .controlDescription resolutionRecommendation: .resolutionRecommendation ``` Sample payloadJSON ``` { "__typename": "Control", "id": "9d7ef6e4-baed-47ba-99ec-a78a801f1e19", "name": "Publicly Exposed Assets with DataFindings ", "controlDescription": "", "resolutionRecommendation": "", "securitySubCategories": [ { "title": "Data Security", "category": { "name": "8 Data Security", "framework": { "name": "Wiz" } } } ] } ``` Port entityJSON ``` { "identifier": "9d7ef6e4-baed-47ba-99ec-a78a801f1e19", "title": "Publicly Exposed Assets with DataFindings", "blueprint": "wizControl", "icon": "Flag", "properties": { "controlDescription": "", "resolutionRecommendation": "" }, "createdAt": "2024-2-6T09:30:57.924Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-2-6T11:49:20.881Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `statusList` | `array` | List of statuses to filter issues by Default: `["OPEN","IN_PROGRESS"]`. | | `severityList` | `array` | List of severities to filter issues by. If empty, all severities are fetched. | | `typeList` | `array` | List of issue types to fetch. If empty, all issue types are fetched. | | `maxPages` | `integer` | Maximum number of pages to fetch for issues. Default: `500`. | Default mappingYAML ``` - kind: serviceTicket selector: query: 'true' port: entity: mappings: blueprint: '"wizServiceTicket"' identifier: .externalId title: .name properties: url: .url ``` Sample payloadJSON ``` { "externalId": "data##wizt-customID##ja63-kx0z-f27x-mpvl", "name": "Security Vulnerability in AWS S3 Bucket", "url": "https://api.wiz.com/wiz/service-tickets/data##wizt-customID##ja63-kx0z-f27x-mpvl" } ``` Port entityJSON ``` { "identifier": "data##wizt-customID##ja63-kx0z-f27x-mpvl", "title": "Security Vulnerability in AWS S3 Bucket", "blueprint": "serviceTicket", "icon": "Book", "properties": { "url": "https://api.wiz.com/wiz/service-tickets/data##wizt-customID##ja63-kx0z-f27x-mpvl" }, "relations": {}, "createdAt": "2023-08-23T07:56:09.903743Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-09-12T08:33:16.327851Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: vulnerability-finding selector: query: 'true' port: entity: mappings: blueprint: '"wizVulnerabilityFinding"' identifier: .id title: .name properties: status: .status severity: .severity categories: .categories version: .version score: .score description: .description detailedName: .detailedName artifactMetadata: .artifactType remediation: .remediation detectionMethod: .detectionMethod environments: .environments link: .link portalUrl: .portalUrl origin: .origin vulnerabilityExternalId: .vulnerabilityExternalId CVEDescription: .CVEDescription hasFix: .hasFix hasExploit: .hasExploit isHighProfileThreat: .isHighProfileThreat updatedAt: .updatedAt resolvedAt: .resolvedAt firstDetectedAt: .firstDetectedAt publishedDate: .publishedDate rootComponent: .rootComponent applicationServices: .applicationServices relations: projects: .projects | map(.id) ``` Sample payloadJSON ``` { "id": "0ac9b058-fe02-5e5c-990f-5d181343d09d", "severity": "LOW", "categories": "DOS", "version": "2.2.4", "detectionMethod": "PACKAGE", "score": 3.7, "status": "OPEN", "detailedName": "apt", "artifactType": { "group": "OS_PACKAGE", "ciComponent": null, "custom": null, "plugin": null, "osPackageManager": "DPKG", "codeLibraryLanguage": null }, "description": "The package `apt` version `2.2.4` was detected in `APT package manager` on a machine running `Debian 11.11` is vulnerable to `CVE-2011-3374`, which exists in `all current versions`.\n\nThe vulnerability was found in the [Official Debian Security Advisories](https://security-tracker.debian.org/tracker/CVE-2011-3374) with vendor severity: `Low` ([NVD](https://nvd.nist.gov/vuln/detail/CVE-2011-3374) severity: `Low`).\n\nThis vulnerability has a known exploit available. Source: [VulnCheck](https://seclists.org/fulldisclosure/2011/Sep/221).\n\nThis vulnerability cannot be remediated because a fix has not been released.", "resolvedAt": null, "updatedAt": "2025-12-02T12:41:24.069936Z", "firstDetectedAt": "2022-11-06T14:05:28.414309Z", "publishedDate": "2019-11-26T00:15:00Z", "remediation": null, "environments": [ "PRODUCTION" ], "link": "https://security-tracker.debian.org/tracker/CVE-2011-3374", "vulnerabilityExternalId": "CVE-2011-3374", "portalUrl": "https://app.wiz.io/explorer/vulnerability-findings#~(entity~(~'0ac9b058-fe02-5e5c-990f-5d181343d09d*2cSECURITY_TOOL_FINDING))", "origin": "WIZ", "CVEDescription": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.", "name": "CVE-2011-3374", "hasFix": false, "hasExploit": true, "isHighProfileThreat": false, "projects": [ { "id": "0f19bcc4-c17b-57d0-a187-db3a6b1a5100", "name": "Project 3" } ], "rootComponent": null, "applicationServices": null } ``` Port entityJSON ``` { "identifier": "0ac9b058-fe02-5e5c-990f-5d181343d09d", "title": "CVE-2011-3374", "properties": { "status": "OPEN", "severity": "LOW", "categories": [ "DOS" ], "version": "2.2.4", "detectionMethod": "PACKAGE", "score": 3.7, "detailedName": "apt", "artifactMetadata": { "group": "OS_PACKAGE", "osPackageManager": "DPKG" }, "description": "The package `apt` version `2.2.4` was detected in `APT package manager` on a machine running `Debian 11.11` is vulnerable to `CVE-2011-3374`, which exists in `all current versions`.\n\nThe vulnerability was found in the [Official Debian Security Advisories](https://security-tracker.debian.org/tracker/CVE-2011-3374) with vendor severity: `Low` ([NVD](https://nvd.nist.gov/vuln/detail/CVE-2011-3374) severity: `Low`).\n\nThis vulnerability has a known exploit available. Source: [VulnCheck](https://seclists.org/fulldisclosure/2011/Sep/221).\n\nThis vulnerability cannot be remediated because a fix has not been released.", "firstDetectedAt": "2022-11-06T14:05:28.414309Z", "publishedDate": "2019-11-26T00:15:00Z", "environments": [ "PRODUCTION" ], "link": "https://security-tracker.debian.org/tracker/CVE-2011-3374", "vulnerabilityExternalId": "CVE-2011-3374", "portalUrl": "https://app.wiz.io/explorer/vulnerability-findings#~(entity~(~'0ac9b058-fe02-5e5c-990f-5d181343d09d*2cSECURITY_TOOL_FINDING))", "origin": "WIZ", "CVEDescription": "It was found that apt-key in apt, all versions, do not correctly validate gpg keys with the master keyring, leading to a potential man-in-the-middle attack.", "hasFix": false, "hasExploit": true, "isHighProfileThreat": false, "updatedAt": "2025-12-02T12:41:24.069936Z" }, "relations": { "projects": [ "0f19bcc4-c17b-57d0-a187-db3a6b1a5100" ] }, "icon": "Wiz" } ``` BlueprintJSON Create in Port ``` { "identifier": "sbomArtifact", "description": "This blueprint represents a Wiz SBOM artifact", "title": "SBOM Artifact", "icon": "Package", "schema": { "properties": { "name": { "icon": "DefaultProperty", "type": "string", "title": "Name" }, "version": { "icon": "DefaultProperty", "type": "string", "title": "Version" }, "packageType": { "icon": "DefaultProperty", "type": "string", "title": "Package Type" }, "packageEcosystem": { "icon": "DefaultProperty", "type": "string", "title": "Package Ecosystem" }, "filePath": { "icon": "DefaultProperty", "type": "string", "title": "File Path" }, "transitivity": { "icon": "DefaultProperty", "type": "string", "title": "Transitivity" }, "validatedInRuntime": { "icon": "DefaultProperty", "type": "boolean", "title": "Validated In Runtime" }, "usedInCodeResult": { "icon": "DefaultProperty", "type": "string", "title": "Used In Code Result" }, "licenses": { "icon": "DefaultProperty", "type": "array", "title": "Licenses" }, "assetType": { "icon": "DefaultProperty", "type": "string", "title": "Asset Type" }, "asset": { "icon": "DefaultProperty", "type": "object", "title": "Asset" } }, "required": [ "name" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "target": "wizRepository", "title": "Repository", "description": "The repository linked to this SBOM artifact when the asset type is a repository branch", "required": false, "many": false } } } ``` Mapping configurationYAML ``` - kind: sbom-artifact selector: query: 'true' port: entity: mappings: identifier: .id title: .name + "-" + (.version // "unknown") | gsub("[^\\p{L}0-9@_.+:\\/=-]"; "") blueprint: '"sbomArtifact"' properties: name: .name version: .version packageEcosystem: .__groupTypeMetadata.osPackageManager // .__groupTypeMetadata.codeLibraryLanguage // .__groupTypeMetadata.plugin // .__groupTypeMetadata.custom // .__groupTypeMetadata.ciComponent // null packageType: .__groupTypeMetadata.group // null filePath: .filePath transitivity: .transitivity validatedInRuntime: .validatedInRuntime usedInCodeResult: .usedInCodeResult licenses: (.licenses // []) | map(.name) assetType: (.asset // {}).__typename asset: .asset relations: repository: if (.asset // {}).__typename == "SBOMArtifactAssetRepositoryBranch" then .asset.repositoryId else null end ``` Sample payloadJSON ``` { "id": "bb3a41b7-b851-5588-ab69-423cb74935c5", "name": "acorn", "version": "8.15.0", "filePath": "/image-exporter/package-lock.json", "transitivity": "TRANSITIVE", "validatedInRuntime": false, "usedInCodeResult": "UNCHECKED", "licenses": [ { "id": "MIT", "name": "MIT" } ], "asset": { "id": "f76f6ea3-7004-5d35-8172-2c4e6df1dc06", "name": "remix/remix/dev", "type": "REPOSITORY_BRANCH", "__typename": "SBOMArtifactAssetRepositoryBranch", "nativeType": "github#repositoryBranch", "repositoryId": "19120bd2-a41f-5f06-9979-7f736691dac4", "cloudPlatform": "GitHub", "repositoryName": "remix/remix", "repositoryExternalId": "github.com##remix/remix" }, "__groupTypeMetadata": { "codeLibraryLanguage": "JAVASCRIPT", "osPackageManager": null, "plugin": null, "ciComponent": null, "custom": null, "group": "CODE_LIBRARY" } } ``` Port entityJSON ``` { "identifier": "bb3a41b7-b851-5588-ab69-423cb74935c5", "title": "acorn-8.15.0", "properties": { "name": "acorn", "version": "8.15.0", "filePath": "/image-exporter/package-lock.json", "transitivity": "TRANSITIVE", "validatedInRuntime": false, "usedInCodeResult": "UNCHECKED", "licenses": [ "MIT" ], "assetType": "SBOMArtifactAssetRepositoryBranch", "asset": { "id": "f76f6ea3-7004-5d35-8172-2c4e6df1dc06", "name": "remix/remix/dev", "type": "REPOSITORY_BRANCH", "__typename": "SBOMArtifactAssetRepositoryBranch", "nativeType": "github#repositoryBranch", "repositoryId": "19120bd2-a41f-5f06-9979-7f736691dac4", "cloudPlatform": "GitHub", "repositoryName": "remix/remix", "repositoryExternalId": "github.com##remix/remix" }, "packageType": "JAVASCRIPT", "groupName": "CODE_LIBRARY" }, "relations": { "repository": "19120bd2-a41f-5f06-9979-7f736691dac4" }, "icon": "Package" } ``` Default mappingYAML ``` - kind: repository selector: query: 'true' port: entity: mappings: blueprint: '"wizRepository"' identifier: .id title: .name properties: url: .url platform: .platform public: .public archived: .archived visibility: .visibility organization: .organization.id branches: .branches | map(.id) relations: projects: .projects | map(.id) ``` Sample payloadJSON ``` { "id": "78342", "name": "payment-service", "url": "https://github.com/org/payment-service", "platform": "GITHUB", "public": false, "archived": false, "visibility": "PRIVATE", "organization": { "id": "org-4821", "name": "acme-engineering" }, "projects":[ { "name": "Project1", "id": "d6ac50bb-aec0-52fc-80ab-bacd7b02f178" } ], "branches": [ { "id": "branch-a1", "name": "main", "url": "https://github.com/org/payment-service/tree/main" }, { "id": "branch-a2", "name": "develop", "url": "https://github.com/org/payment-service/tree/develop" } ] } ``` Port entityJSON ``` { "identifier": "78342", "title": "payment-service", "icon": "Wiz", "properties": { "url": "https://github.com/org/payment-service", "platform": "GITHUB", "public": false, "archived": false, "visibility": "PRIVATE", "organization": "acme-engineering", "branches": [ "main", "develop" ] }, "relations": { "projects": ["d6ac50bb-aec0-52fc-80ab-bacd7b02f178"] } } ``` Default mappingYAML ``` - kind: technology selector: query: 'true' port: entity: mappings: blueprint: '"wizTechnology"' identifier: .id title: .name properties: description: .description categories: .categories | map(.name) usage: .usage status: .status risk: .risk note: .note ownerName: .ownerName businessModel: .businessModel popularity: .popularity projectCount: .projectCount codeRepoCount: .codeRepoCount isCloudService: .isCloudService supportedOperatingSystems: .supportedOperatingSystems ``` Sample payloadJSON ``` { "id": "10071", "name": ".NET Core SDK", "description": ".NET Core SDK is a set of libraries and tools that allow developers to create .NET Core applications and libraries. It includes the .NET Core runtime for running applications, but also provides additional capabilities for building, testing, and debugging .NET Core applications. The SDK also includes a command line interface (CLI) for performing various tasks such as project creation, code compilation, and package management.", "categories": [ { "name": "Frameworks & Libraries" } ], "usage": "COMMON", "status": "UNREVIEWED", "risk": "MEDIUM", "note": null, "ownerName": "Microsoft Corporation", "businessModel": "COMMERCIAL_OPEN_SOURCE", "popularity": "VERY_COMMON", "projectCount": 0, "codeRepoCount": 0, "isCloudService": false, "supportedOperatingSystems": [ "WINDOWS", "LINUX" ] } ``` Port entityJSON ``` { "identifier": "12306", "title": "AWS App Mesh", "properties": { "description": "AWS App Mesh is a service mesh that provides application-level networking to make it easy for your services to communicate with each other across multiple types of compute infrastructure. It standardizes how your services communicate, giving you end-to-end visibility and helping to ensure high availability for your applications.", "categories": [ "Cloud Networking Services" ], "usage": "COMMON", "status": "UNREVIEWED", "risk": "HIGH", "ownerName": "Amazon Web Services, Inc.", "businessModel": "COMMERCIAL_PROPRIETARY", "projectCount": 0, "codeRepoCount": 0, "isCloudService": true }, "relations": {}, "icon": "Wiz" } ``` Default mappingYAML ``` - kind: hosted-technology selector: query: 'true' port: entity: mappings: blueprint: '"wizHostedTechnology"' identifier: .id title: .name properties: detectionMethods: .detectionMethods installedPackages: .installedPackages firstSeen: .firstSeen updatedAt: .updatedAt cpe: .cpe relations: technology: .technology.id ``` Sample payloadJSON ``` { "id": "08be2ce6-e850-5bac-b116-bb63f991d2bd", "name": "Linux Debian (docker.io/httpd@96b1e8f6)", "technology": { "id": "4370", "name": "Linux Debian" }, "resource": { "id": "b8ca4430-1676-54cd-a342-b9f6110cdfa9", "name": "docker.io/httpd@96b1e8f6" }, "detectionMethods": [ "OS" ], "installedPackages": [ "adduser (3.152)", "apt (3.0.3)", "base-files (13.8+deb13u3)", "base-passwd (3.6.7)", "bash (5.2.37-2+b7)", "bsdutils (1:2.41-5)", "ca-certificates (20250419)", "coreutils (9.7-3)", "dash (0.5.12-12)", "debconf (1.5.91)", "debian-archive-keyring (2025.1)", "debianutils (5.23.2)", "diffutils (1:3.10-4)", "dpkg (1.22.21)", "findutils (4.10.0-3)", "gcc-14-base (14.2.0-19)", "grep (3.11-4)", "gzip (1.13-1)", "hostname (3.25)", "init-system-helpers (1.69~deb13u1)", "libacl1 (2.3.2-2+b1)", "libapr1t64 (1.7.5-1)", "libaprutil1-ldap (1.6.3-3+b1)", "libaprutil1t64 (1.6.3-3+b1)", "libapt-pkg7.0 (3.0.3)", "libattr1 (1:2.5.2-3)", "libaudit-common (1:4.0.2-2)", "libaudit1 (1:4.0.2-2+b2)", "libblkid1 (2.41-5)", "libbrotli1 (1.1.0-2+b7)", "libbsd0 (0.12.2-2)", "libbz2-1.0 (1.0.8-6)", "libc-bin (2.41-12+deb13u1)", "libc6 (2.41-12+deb13u1)", "libcap-ng0 (0.8.5-4+b1)", "libcap2 (1:2.75-10+b3)", "libcom-err2 (1.47.2-3+b7)", "libcrypt1 (1:4.4.38-1)", "libcurl4t64 (8.14.1-2+deb13u2)", "libdb5.3t64 (5.3.28+dfsg2-9)", "libdebconfclient0 (0.280)", "libexpat1 (2.7.1-2)", "libffi8 (3.4.8-2)", "libgcc-s1 (14.2.0-19)", "libgdbm6t64 (1.24-2)", "libgmp10 (2:6.3.0+dfsg-3)", "libgnutls30t64 (3.8.9-3+deb13u2)", "libgssapi-krb5-2 (1.21.3-5)", "libhogweed6t64 (3.10.1-1)", "libidn2-0 (2.3.8-2)", "libjansson4 (2.14-2+b3)", "libk5crypto3 (1.21.3-5)", "libkeyutils1 (1.6.3-6)", "libkrb5-3 (1.21.3-5)", "libkrb5support0 (1.21.3-5)", "liblastlog2-2 (2.41-5)", "libldap-common (2.6.10+dfsg-1)", "libldap2 (2.6.10+dfsg-1)", "liblua5.2-0 (5.2.4-3+b3)", "liblz4-1 (1.10.0-4)", "liblzma5 (5.8.1-1)", "libmd0 (1.1.0-2+b1)", "libmount1 (2.41-5)", "libnettle8t64 (3.10.1-1)", "libnghttp2-14 (1.64.0-1.1)", "libnghttp3-9 (1.8.0-1)", "libp11-kit0 (0.25.5-3)", "libpam-modules (1.7.0-5)", "libpam-modules-bin (1.7.0-5)", "libpam-runtime (1.7.0-5)", "libpam0g (1.7.0-5)", "libpcre2-8-0 (10.46-1~deb13u1)", "libpsl5t64 (0.21.2-1.1+b1)", "librtmp1 (2.4+20151223.gitfa8646d.1-2+b5)", "libsasl2-2 (2.1.28+dfsg1-9)", "libsasl2-modules-db (2.1.28+dfsg1-9)", "libseccomp2 (2.6.0-2)", "libselinux1 (3.8.1-1)", "libsemanage-common (3.8.1-1)", "libsemanage2 (3.8.1-1)", "libsepol2 (3.8.1-1)", "libsmartcols1 (2.41-5)", "libsqlite3-0 (3.46.1-7)", "libssh2-1t64 (1.11.1-1)", "libssl3t64 (3.5.4-1~deb13u2)", "libstdc++6 (14.2.0-19)", "libsystemd0 (257.9-1~deb13u1)", "libtasn1-6 (4.20.0-2)", "libtinfo6 (6.5+20250216-2)", "libudev1 (257.9-1~deb13u1)", "libunistring5 (1.3-2)", "libuuid1 (2.41-5)", "libxml2 (2.12.7+dfsg+really2.9.14-2.1+deb13u2)", "libxxhash0 (0.8.3-2)", "libzstd1 (1.5.7+dfsg-1)", "login (1:4.16.0-2+really2.41-5)", "login.defs (1:4.17.4-2)", "mawk (1.3.4.20250131-1)", "mount (2.41-5)", "ncurses-base (6.5+20250216-2)", "ncurses-bin (6.5+20250216-2)", "openssl (3.5.4-1~deb13u2)", "openssl-provider-legacy (3.5.4-1~deb13u2)", "passwd (1:4.17.4-2)", "perl-base (5.40.1-6)", "sed (4.9-2)", "sqv (1.3.0-3+b2)", "sysvinit-utils (3.14-4)", "tar (1.35+dfsg-3.1)", "tzdata (2025b-4+deb13u1)", "util-linux (2.41-5)", "zlib1g (1:1.3.dfsg+really1.3.1-1+b1)" ], "firstSeen": "2026-03-04T12:25:53.351929Z", "updatedAt": "2026-03-04T12:25:53.351929Z", "cpe": "cpe:/o:debian:debian_linux:13.3" } ``` Port entityJSON ``` { "identifier": "08be2ce6-e850-5bac-b116-bb63f991d2bd", "title": "Linux Debian (docker.io/httpd@96b1e8f6)", "properties": { "detectionMethods": [ "OS" ], "installedPackages": [ "adduser (3.152)", "apt (3.0.3)", "base-files (13.8+deb13u3)", "base-passwd (3.6.7)", "bash (5.2.37-2+b7)", "bsdutils (1:2.41-5)", "ca-certificates (20250419)", "coreutils (9.7-3)", "dash (0.5.12-12)", "debconf (1.5.91)", "debian-archive-keyring (2025.1)", "debianutils (5.23.2)", "diffutils (1:3.10-4)", "dpkg (1.22.21)", "findutils (4.10.0-3)", "gcc-14-base (14.2.0-19)", "grep (3.11-4)", "gzip (1.13-1)", "hostname (3.25)", "init-system-helpers (1.69~deb13u1)", "libacl1 (2.3.2-2+b1)", "libapr1t64 (1.7.5-1)", "libaprutil1-ldap (1.6.3-3+b1)", "libaprutil1t64 (1.6.3-3+b1)", "libapt-pkg7.0 (3.0.3)", "libattr1 (1:2.5.2-3)", "libaudit-common (1:4.0.2-2)", "libaudit1 (1:4.0.2-2+b2)", "libblkid1 (2.41-5)", "libbrotli1 (1.1.0-2+b7)", "libbsd0 (0.12.2-2)", "libbz2-1.0 (1.0.8-6)", "libc-bin (2.41-12+deb13u1)", "libc6 (2.41-12+deb13u1)", "libcap-ng0 (0.8.5-4+b1)", "libcap2 (1:2.75-10+b3)", "libcom-err2 (1.47.2-3+b7)", "libcrypt1 (1:4.4.38-1)", "libcurl4t64 (8.14.1-2+deb13u2)", "libdb5.3t64 (5.3.28+dfsg2-9)", "libdebconfclient0 (0.280)", "libexpat1 (2.7.1-2)", "libffi8 (3.4.8-2)", "libgcc-s1 (14.2.0-19)", "libgdbm6t64 (1.24-2)", "libgmp10 (2:6.3.0+dfsg-3)", "libgnutls30t64 (3.8.9-3+deb13u2)", "libgssapi-krb5-2 (1.21.3-5)", "libhogweed6t64 (3.10.1-1)", "libidn2-0 (2.3.8-2)", "libjansson4 (2.14-2+b3)", "libk5crypto3 (1.21.3-5)", "libkeyutils1 (1.6.3-6)", "libkrb5-3 (1.21.3-5)", "libkrb5support0 (1.21.3-5)", "liblastlog2-2 (2.41-5)", "libldap-common (2.6.10+dfsg-1)", "libldap2 (2.6.10+dfsg-1)", "liblua5.2-0 (5.2.4-3+b3)", "liblz4-1 (1.10.0-4)", "liblzma5 (5.8.1-1)", "libmd0 (1.1.0-2+b1)", "libmount1 (2.41-5)", "libnettle8t64 (3.10.1-1)", "libnghttp2-14 (1.64.0-1.1)", "libnghttp3-9 (1.8.0-1)", "libp11-kit0 (0.25.5-3)", "libpam-modules (1.7.0-5)", "libpam-modules-bin (1.7.0-5)", "libpam-runtime (1.7.0-5)", "libpam0g (1.7.0-5)", "libpcre2-8-0 (10.46-1~deb13u1)", "libpsl5t64 (0.21.2-1.1+b1)", "librtmp1 (2.4+20151223.gitfa8646d.1-2+b5)", "libsasl2-2 (2.1.28+dfsg1-9)", "libsasl2-modules-db (2.1.28+dfsg1-9)", "libseccomp2 (2.6.0-2)", "libselinux1 (3.8.1-1)", "libsemanage-common (3.8.1-1)", "libsemanage2 (3.8.1-1)", "libsepol2 (3.8.1-1)", "libsmartcols1 (2.41-5)", "libsqlite3-0 (3.46.1-7)", "libssh2-1t64 (1.11.1-1)", "libssl3t64 (3.5.4-1~deb13u2)", "libstdc++6 (14.2.0-19)", "libsystemd0 (257.9-1~deb13u1)", "libtasn1-6 (4.20.0-2)", "libtinfo6 (6.5+20250216-2)", "libudev1 (257.9-1~deb13u1)", "libunistring5 (1.3-2)", "libuuid1 (2.41-5)", "libxml2 (2.12.7+dfsg+really2.9.14-2.1+deb13u2)", "libxxhash0 (0.8.3-2)", "libzstd1 (1.5.7+dfsg-1)", "login (1:4.16.0-2+really2.41-5)", "login.defs (1:4.17.4-2)", "mawk (1.3.4.20250131-1)", "mount (2.41-5)", "ncurses-base (6.5+20250216-2)", "ncurses-bin (6.5+20250216-2)", "openssl (3.5.4-1~deb13u2)", "openssl-provider-legacy (3.5.4-1~deb13u2)", "passwd (1:4.17.4-2)", "perl-base (5.40.1-6)", "sed (4.9-2)", "sqv (1.3.0-3+b2)", "sysvinit-utils (3.14-4)", "tar (1.35+dfsg-3.1)", "tzdata (2025b-4+deb13u1)", "util-linux (2.41-5)", "zlib1g (1:1.3.dfsg+really1.3.1-1+b1)" ], "firstSeen": "2026-03-04T12:25:53.351929Z", "updatedAt": "2026-03-04T12:25:53.351929Z", "cpe": "cpe:/o:debian:debian_linux:13.3" }, "relations": { "technology": "4370" }, "icon": "Wiz" } ``` ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found on the Wiz [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/examples/.md). --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations: ### Project[​](#project "Direct link to Project") **Project blueprint (click to expand)** Create in Port ``` { "identifier": "wizProject", "description": "This blueprint represents a wiz project", "title": "Wiz Project", "icon": "Box", "schema": { "properties": { "archived": { "type": "boolean", "title": "Archived?", "description": "Is the project archived?" }, "businessUnit": { "type": "string", "title": "Business Unit", "description": "the business unit of the project" }, "description": { "type": "string", "title": "Description", "description": "the project description" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "issues": { "target": "wizIssue", "title": "Issues", "description": "The issues affecting this project", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` resources: - kind: project selector: query: 'true' port: entity: mappings: blueprint: '"wizProject"' identifier: .id title: .name properties: archived: .archived businessUnit: .businessUnit description: .description ``` * Include archived projects * Include business impact The `includeArchived` selector controls whether to include archived projects in the integration. If not specified, archived projects are not included. ``` - kind: project selector: query: 'true' includeArchived: true ``` The `impact` selector controls whether to filter projects by business impact in the integration. If not specified, no business impact filters are applied. ``` - kind: project selector: query: 'true' impact: "HBI" ``` ### Control[​](#control "Direct link to Control") **Control blueprint (click to expand)** Create in Port ``` { "identifier": "wizControl", "description": "This blueprint represents a wiz source rule", "title": "Wiz Control", "icon": "Flag", "schema": { "properties": { "controlDescription": { "type": "string", "title": "Control Description", "description": "the control description" }, "resolutionRecommendation": { "type": "string", "title": "Control Recommendation", "description": "the control recommendation on resolving issues" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: control selector: query: 'true' port: entity: mappings: blueprint: '"wizControl"' identifier: .id title: .name properties: controlDescription: .controlDescription resolutionRecommendation: .resolutionRecommendation ``` ### Issue[​](#issue "Direct link to Issue") **Issue blueprint (click to expand)** ``` { "identifier": "wizIssue", "description": "This blueprint represents a wiz issue", "title": "Wiz Issue", "icon": "Alert", "schema": { "properties": { "url": { "type": "string", "title": "Issue URL", "format": "url", "description": "the link to the issue" }, "status": { "title": "Status", "type": "string", "enum": [ "OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED" ], "enumColors": { "OPEN": "blue", "IN_PROGRESS": "orange", "RESOLVED": "green", "REJECTED": "darkGray" } }, "severity": { "title": "Severity", "type": "string", "enum": [ "INFORMATIONAL", "LOW", "MEDIUM", "HIGH", "CRITICAL" ], "enumColors": { "INFORMATIONAL": "blue", "LOW": "yellow", "MEDIUM": "orange", "HIGH": "red", "CRITICAL": "red" } }, "vulnerabilityType": { "title": "Vulnerability Type", "type": "string" }, "wizIssueID": { "title": "Wiz Issue ID", "type": "string" }, "cloudResourceType": { "title": "Cloud Resource Type", "type": "string" }, "resourceName": { "title": "Resource Name", "type": "string" }, "cloudPlatform": { "title": "Cloud Platform", "type": "string" }, "linkToResource": { "title": "Link to Cloud Resource", "type": "string", "format": "url" }, "cloudResourceID": { "title": "Cloud Resource ID", "type": "string" }, "cloudRegion": { "title": "Cloud Region", "type": "string" }, "resourceGroupExternalId": { "title": "Resource Group External ID", "type": "string" }, "subscriptionExternalId": { "title": "Subscription External ID", "type": "string" }, "subscriptionName": { "title": "Subscription Name", "type": "string" }, "subscriptionTags": { "title": "Subscription Tags", "type": "object" }, "resourceTags": { "title": "Resource Tags", "type": "object" }, "vulnerability": { "title": "Vulnerability", "type": "object", "description": "The identified security risk" }, "notes": { "title": "Notes", "type": "array" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "dueAt": { "title": "Due At", "type": "string", "format": "date-time" }, "resolvedAt": { "title": "Resolved At", "type": "string", "format": "date-time" }, "statusChangedAt": { "title": "Status ChangedAt", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "projects": { "target": "wizProject", "title": "Affected Projects", "description": "The projects affected by this issue", "required": false, "many": true }, "serviceTickets": { "target": "wizServiceTicket", "title": "Service Tickets", "description": "The service tickets belonging to this issue", "required": false, "many": true }, "control": { "target": "wizControl", "title": "Control", "description": "The control that flagged this issue", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: issue selector: query: 'true' port: entity: mappings: blueprint: '"wizIssue"' identifier: .id title: .entitySnapshot.name + " | " + .entitySnapshot.type properties: url: .id as $id | "https://app.wiz.io/issues#~(issue~'" + $id + ")" status: .status severity: .severity vulnerabilityType: .type notes: .notes wizIssueID: .entitySnapshot.id cloudResourceType: .entitySnapshot.type resourceName: .entitySnapshot.name cloudPlatform: .entitySnapshot.cloudPlatform linkToResource: if .entitySnapshot.cloudProviderURL == "" then null else .entitySnapshot.cloudProviderURL end cloudResourceID: .entitySnapshot.providerId cloudRegion: .entitySnapshot.region resourceGroupExternalId: .entitySnapshot.resourceGroupExternalId subscriptionExternalId: .entitySnapshot.subscriptionExternalId subscriptionName: .entitySnapshot.subscriptionName subscriptionTags: .entitySnapshot.subscriptionTags resourceTags: .entitySnapshot.tags vulnerability: .entitySnapshot createdAt: .createdAt updatedAt: .updatedAt statusChangedAt: .statusChangedAt resolvedAt: .resolvedAt relations: projects: .projects[].id serviceTickets: .serviceTickets[].externalId control: .sourceRule.id ``` ### Service Ticket[​](#service-ticket "Direct link to Service Ticket") **Service Ticket blueprint (click to expand)** ``` { "identifier": "wizServiceTicket", "description": "This blueprint represents a wiz service ticket", "title": "Wiz Service Ticket", "icon": "Book", "schema": { "properties": { "url": { "type": "string", "title": "Ticket URL", "format": "url", "description": "the service ticket URL" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: serviceTicket selector: query: 'true' port: entity: mappings: blueprint: '"wizServiceTicket"' identifier: .externalId title: .name properties: url: .url ``` ### Vulnerability[​](#vulnerability "Direct link to Vulnerability") **Vulnerability blueprint (click to expand)** Create in Port ``` { "identifier": "wizVulnerabilityFinding", "description": "This blueprint represents a wiz vulnerability finding", "title": "Wiz Vulnerability Finding", "icon": "Wiz", "schema": { "properties": { "status": { "title": "Status", "type": "string", "enum": [ "OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED" ], "enumColors": { "OPEN": "red", "IN_PROGRESS": "yellow", "RESOLVED": "green", "REJECTED": "lightGray" } }, "severity": { "title": "Severity", "type": "string", "enum": [ "LOW", "MEDIUM", "HIGH", "CRITICAL", "NONE" ], "enumColors": { "LOW": "olive", "MEDIUM": "yellow", "HIGH": "red", "CRITICAL": "red", "NONE": "lightGray" } }, "categories": { "title": "Categories", "type": "array" }, "version": { "title": "Version", "type": "string" }, "detectionMethod": { "title": "Detection Method", "type": "string" }, "score": { "title": "Score", "type": "number" }, "description": { "title": "Description", "type": "string" }, "firstDetectedAt": { "title": "First Detected At", "type": "string", "format": "date-time" }, "publishedDate": { "title": "Published Date", "type": "string", "format": "date-time" }, "remediation": { "title": "Remediation", "type": "string" }, "environments": { "title": "Environments", "type": "array" }, "link": { "title": "Link", "type": "string", "format": "url" }, "vulnerabilityExternalId": { "title": "Vulnerability External ID", "type": "string" }, "portalUrl": { "title": "Portal URL", "type": "string", "format": "url" }, "origin": { "title": "Origin", "type": "string" }, "CVEDescription": { "title": "CVE Description", "type": "string" }, "hasFix": { "title": "Has Fix", "type": "boolean" }, "hasExploit": { "title": "Has Exploit", "type": "boolean" }, "isHighProfileThreat": { "title": "Is High Profile Threat", "type": "boolean" }, "rootComponent": { "title": "Root Component", "type": "object" }, "applicationServices": { "title": "Application Services", "type": "array" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "resolvedAt": { "title": "Resolved At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "projects": { "target": "wizProject", "title": "Projects", "description": "The projects affected by this vulnerability finding", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` resources: - kind: vulnerability-finding selector: query: 'true' port: entity: mappings: blueprint: '"wizVulnerabilityFinding"' identifier: .id title: .name properties: status: .status severity: .severity categories: .categories version: .version score: .score description: .description remediation: .remediation detectionMethod: .detectionMethod environments: .environments link: .link portalUrl: .portalUrl origin: .origin vulnerabilityExternalId: .vulnerabilityExternalId CVEDescription: .CVEDescription hasFix: .hasFix hasExploit: .hasExploit isHighProfileThreat: .isHighProfileThreat updatedAt: .updatedAt resolvedAt: .resolvedAt firstDetectedAt: .firstDetectedAt publishedDate: .publishedDate rootComponent: .rootComponent applicationServices: .applicationServices relations: projects: .projects | map(.id) ``` The following selectors can be used to filter the data fetched from the Wiz API for the **Vulnerability** kind: * `severityList`: List of severity values to filter vulnerability findings by. * `statusList`: List of status values to filter vulnerability findings by. * `maxPages`: Maximum number of pages to fetch from the Wiz API. * `parallelism`: Optional parallel pagination settings for vulnerability findings resyncs. - Include severity list - Include status list - Include max pages - Enable parallel pagination The `severityList` selector controls the severity of vulnerabilities fetched from the Wiz API. If not specified, no severity filtering is applied, and all vulnerability severity are ingested into the catalog. ``` - kind: vulnerability-finding selector: query: 'true' severityList: ["LOW", "MEDIUM", "HIGH", "CRITICAL", "NONE"] ``` The `statusList` selector optionally restricts returned results to specific vulnerability statuses. If not specified, the default statuses (`OPEN`, `IN_PROGRESS`) are applied. ``` - kind: vulnerability-finding selector: query: 'true' typeList: ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"] ``` The `maxPages` selector is used to control how many pages the integration fetches from the Wiz API. The default value is **500**, which ingests up to 50,000 results since each page contains 100 results. ``` - kind: vulnerability-finding selector: query: 'true' maxPages: 100 # default is 500 ``` When you ingest large volumes of vulnerability findings, a single sequential cursor chain can make full resyncs slow. Set the optional `parallelism` selector to split the query into concurrent partitions and crawl them in parallel. When `parallelism` is omitted, the integration uses the default sequential pagination. This feature is available starting from Wiz integration version `0.3.0`. The `parallelism` selector accepts the following options. The values shown in the example below are the defaults applied when you enable `parallelism`: * `strategy`: Partition strategy. `date` uses `firstSeenAt` windows, `severity` shards by severity, and `auto` prefers date windows then falls back to severity shards. * `lookbackDays`: How many days back to create date partitions when using the date strategy. * `dateIntervalDays`: Size of each date partition window in days. * `apiRequestsPerSecond`: Maximum Wiz API requests per second during parallel pagination. Wiz allows up to 10 per service account. * `maxPartitionEntities`: Maximum entities allowed in a partition before it is split further during probe/refine. ``` - kind: vulnerability-finding selector: query: 'true' parallelism: strategy: auto lookbackDays: 365 dateIntervalDays: 30 apiRequestsPerSecond: 10 maxPartitionEntities: 500 ``` ### Repository[​](#repository "Direct link to Repository") **Repository blueprint (click to expand)** ``` { "identifier": "wizRepository", "description": "This blueprint represents a wiz repository", "title": "Wiz Repository", "icon": "Wiz", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "platform": { "title": "Platform", "type": "string" }, "public": { "title": "Public", "type": "boolean" }, "archived": { "title": "Archived", "type": "boolean" }, "visibility": { "title": "Visibility", "type": "string" }, "organization": { "title": "Organization", "type": "string" }, "branches": { "title": "Branches", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: repository selector: query: 'true' port: entity: mappings: blueprint: '"wizRepository"' identifier: .id title: .name properties: url: .url platform: .platform public: .public archived: .archived visibility: .visibility organization: .organization.id branches: .branches | map(.id) ``` ### SBOM Artifact[​](#sbom-artifact "Direct link to SBOM Artifact") **SBOM artifact blueprint (click to expand)** ``` { "identifier": "sbomArtifact", "description": "This blueprint represents a Wiz SBOM artifact", "title": "SBOM Artifact", "icon": "Package", "schema": { "properties": { "name": { "icon": "DefaultProperty", "type": "string", "title": "Name" }, "version": { "icon": "DefaultProperty", "type": "string", "title": "Version" }, "packageType": { "icon": "DefaultProperty", "type": "string", "title": "Package Type" }, "packageEcosystem": { "icon": "DefaultProperty", "type": "string", "title": "Package Ecosystem" }, "filePath": { "icon": "DefaultProperty", "type": "string", "title": "File Path" }, "transitivity": { "icon": "DefaultProperty", "type": "string", "title": "Transitivity" }, "validatedInRuntime": { "icon": "DefaultProperty", "type": "boolean", "title": "Validated In Runtime" }, "usedInCodeResult": { "icon": "DefaultProperty", "type": "string", "title": "Used In Code Result" }, "licenses": { "icon": "DefaultProperty", "type": "array", "title": "Licenses" }, "assetType": { "icon": "DefaultProperty", "type": "string", "title": "Asset Type" }, "asset": { "icon": "DefaultProperty", "type": "object", "title": "Asset" } }, "required": [ "name" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "target": "wizRepository", "title": "Repository", "description": "The repository linked to this SBOM artifact when the asset type is a repository branch", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: sbom-artifact selector: query: 'true' port: entity: mappings: identifier: .id title: .name + "-" + (.version // "unknown") | gsub("[^\\p{L}0-9@_.+:\\/=-]"; "") blueprint: '"sbomArtifact"' properties: name: .name version: .version packageEcosystem: .__groupTypeMetadata.osPackageManager // .__groupTypeMetadata.codeLibraryLanguage // .__groupTypeMetadata.plugin // .__groupTypeMetadata.custom // .__groupTypeMetadata.ciComponent // null packageType: .__groupTypeMetadata.group // null filePath: .filePath transitivity: .transitivity validatedInRuntime: .validatedInRuntime usedInCodeResult: .usedInCodeResult licenses: (.licenses // []) | map(.name) assetType: (.asset // {}).__typename asset: .asset relations: repository: if (.asset // {}).__typename == "SBOMArtifactAssetRepositoryBranch" then .asset.repositoryId else null end ``` The following selectors can be used to filter the data fetched from the Wiz API for the **SBOM Artifact** kind: * `groupList`: List of SBOM artifact groups to fetch. * `maxPages`: Maximum number of pages to fetch from the Wiz API. * `resource`: Optional resource-level filters for SBOM artifacts. If omitted, SBOM artifacts are fetched without resource-level filtering. - Include group list - Include max pages - Include resource filters The `groupList` selector controls the group of SBOM artifacts fetched from the Wiz API. If not specified, all groups are ingested into the catalog. ``` - kind: sbom-artifact selector: query: 'true' groupList: ["CODE_LIBRARY", "OS_PACKAGE", "PLUGIN", "CUSTOM", "CI_COMPONENT"] ``` The `maxPages` selector is used to control how many pages the integration fetches from the Wiz API. The default value is **500**. Increasing this value can cause a spike in SBOM artifacts in the catalog and may increase the time required to sync all artifacts. Increase it only when necessary. ``` - kind: sbom-artifact selector: query: 'true' maxPages: 100 # default is 500 ``` The `resource` selector applies optional resource-level filters to SBOM artifacts. Below are the filter properties: * `cloudPlatform`: List of resource cloud platforms to filter by. Accepted values are `GCP`, `AWS`, `Azure`, `OCI`, `Alibaba`, `vSphere`, `OpenStack`, `AKS`, `EKS`, `GKE`, `Kubernetes`, `OpenShift`, `OKE`, `Linode`, `AzureDevOps`, `GitHub`, `GitLab`, `Bitbucket`, `BitbucketCloud`, `Terraform`, `OpenAI`, `Snowflake`, `MongoDBAtlas`, `Databricks`, `Okta`, `Cloudflare`, `Akamai`, `Microsoft365`, `Wiz`, `ACK`, `SelfHosted`, `LKE`, `IBM`, `ServiceNow`, `Salesforce`, `Vercel`, `GenericDB`, `DigitalOcean`, `TencentCloud`, `OVHCloud`, `Crusoe`, `Nebius`, `Slack`, `CircleCI`, `Jenkins`, `MicrosoftPowerPlatform`, and `Backstage`. * `scanSource`: Resource scan source to filter by. Accepted values are either `WORKLOAD` or `CODE`. Leave empty to include both. * `status`: List of resource statuses to filter by. Accepted values are `Active`, `Inactive`, and `Error`. Leave empty to include all statuses. * `region`: List of resource regions to filter by. Use provider region strings such as `us-east-1` and `eu-west-1`. ``` - kind: sbom-artifact selector: query: 'true' resource: cloudPlatform: ["GitLab", "GitHub", "Bitbucket"] scanSource: "CODE" status: ["Active", "Error"] region: ["us-east-1", "eu-west-1"] ``` ### Technology[​](#technology "Direct link to Technology") **Technology blueprint (click to expand)** ``` { "identifier": "wizTechnology", "description": "This blueprint represents a wiz technology", "title": "Wiz Technology", "icon": "Wiz", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "categories": { "title": "Categories", "type": "array" }, "usage": { "title": "Usage", "type": "string" }, "status": { "title": "Status", "type": "string" }, "risk": { "title": "Risk", "type": "string" }, "note": { "title": "Note", "type": "string" }, "ownerName": { "title": "Owner Name", "type": "string" }, "businessModel": { "title": "Business Model", "type": "string" }, "popularity": { "title": "Popularity", "type": "number" }, "projectCount": { "title": "Project Count", "type": "number" }, "codeRepoCount": { "title": "Code Repo Count", "type": "number" }, "isCloudService": { "title": "Is Cloud Service", "type": "boolean" }, "supportedOperatingSystems": { "title": "Supported Operating Systems", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: technology selector: query: 'true' port: entity: mappings: blueprint: '"wizTechnology"' identifier: .id title: .name properties: description: .description categories: .categories | map(.name) usage: .usage status: .status risk: .risk note: .note ownerName: .ownerName businessModel: .businessModel popularity: .popularity projectCount: .projectCount codeRepoCount: .codeRepoCount isCloudService: .isCloudService supportedOperatingSystems: .supportedOperatingSystems ``` ### Hosted technology[​](#hosted-technology "Direct link to Hosted technology") **Hosted technology blueprint (click to expand)** ``` { "identifier": "wizHostedTechnology", "description": "This blueprint represents a wiz hosted technology", "title": "Wiz Hosted Technology", "icon": "Wiz", "schema": { "properties": { "detectionMethods": { "title": "Detection Methods", "type": "array" }, "installedPackages": { "title": "Installed Packages", "type": "array" }, "firstSeen": { "title": "First Seen", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "cpe": { "title": "CPE", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "technology": { "target": "wizTechnology", "title": "Technology", "description": "The technology that this hosted technology is associated with", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: hosted-technology selector: query: 'true' port: entity: mappings: blueprint: '"wizHostedTechnology"' identifier: .id title: .name properties: detectionMethods: .detectionMethods installedPackages: .installedPackages firstSeen: .firstSeen updatedAt: .updatedAt cpe: .cpe relations: technology: .technology.id ``` ### Advanced configuration[​](#advanced-configuration "Direct link to Advanced configuration") The following selectors can be used to filter the data fetched from the Wiz API for **Issue**, **Control** and **Service Ticket** kinds: * `severityList`: List of severity values to filter issues by. * `typeList`: List of type values to filter issues by. * `maxPages`: Maximum number of pages to fetch from the Wiz API. - Include severity list - Include type list - Include max pages The `severityList` selector controls whether to filter results by severity of the issues. If not specified, no severity filtering is applied. This selector is valid for the `issues`, `serviceTicket`, and `serviceControl` kinds. ``` - kind: issue # one of [issue, serviceControl, serviceTicket] kinds selector: query: 'true' severityList: ["HIGH", "CRITICAL", "INFORMATIONAL"] ``` The `typeList` selector optionally restricts returned results to specific issue types. If not specified, no type filtering is applied. This selector is valid for the `issues`, `serviceTicket`, and `serviceControl` kinds. ``` - kind: issue # one of the supported kinds: issue, serviceControl, serviceTicket selector: query: 'true' typeList: ["TOXIC_COMBINATION", "THREAT_DETECTION"] ``` Wiz issues can grow to an extremely large volume, which may significantly increase the time required to complete a resync for the integration. To help manage this, use the `maxPages` selector to control how many pages the integration fetches from the Wiz API. The default value is **500**, which ingests up to 50,000 results since each page contains 100 results. ``` - kind: issue # one of the supported kinds: issue, serviceControl, serviceTicket selector: query: 'true' maxPages: 100 # default is 500 ``` Performance impact Setting high values for `maxPages` may significantly prolong the sync process. Consider increasing this value only when it is truly necessary. ### Migration from SBOM artifacts detected from vulnerabilities[​](#migration-from-sbom-artifacts-detected-from-vulnerabilities "Direct link to Migration from SBOM artifacts detected from vulnerabilities") Prior to version `0.2.16` of the Wiz integration, you could define an `sbomPackage` blueprint to store SBOM artifacts discovered through vulnerabilities. In that model, the mapping used the `vulnerability-finding` kind. This approach only ingests a subset of your SBOM inventory, because packages without vulnerabilities are not fetched into the catalog. It also has a limited data model, so you cannot map an SBOM package to other components. Starting from version `0.2.16`, this approach is deprecated in favor of the `sbom-artifact` kind, which ingests artifacts from your SBOM inventory. Use the [SBOM artifact section](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/examples/.md?method=hosted\&step=choose-method#sbom-artifact) to configure the new mapping. The following blueprint and mapping show the deprecated configuration: **SBOM package blueprint (click to expand)** Create in Port ``` { "identifier": "sbomPackage", "description": "This blueprint represents a software bill of materials (SBOM) package", "title": "SBOM Package", "icon": "Package", "schema": { "properties": { "package": { "icon": "DefaultProperty", "type": "string", "title": "Package" }, "version": { "icon": "DefaultProperty", "type": "string", "title": "Version" }, "manager": { "icon": "DefaultProperty", "type": "string", "title": "Manager" } }, "required": [ "package", "version" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: vulnerability-finding selector: query: '.detailedName != null and .version != null' port: entity: mappings: identifier: .detailedName + "-" + .version | gsub("[^\\p{L}0-9@_.+:\\/=-]"; "") title: .detailedName + "-" + .version | gsub("[^\\p{L}0-9@_.+:\\/=-]"; "") blueprint: '"sbomPackage"' properties: version: .version package: .detailedName manager: .artifactType.osPackageManager // .artifactType.codeLibraryLanguage ``` --- # Kafka ![Kafka icon](/img/guides/icons/Kafka.svg)![Kafka icon](/img/guides/icons/dark/Kafka.svg) Loading version... Port's Kafka integration allows you to model Kafka resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Cluster](https://kafka.apache.org/documentation/#adminclientconfigs)[Broker](https://kafka.apache.org/documentation/#brokerconfigs)[Topic](https://kafka.apache.org/documentation/#topicconfigs)[Consumer Group](https://kafka.apache.org/documentation/#consumerconfigs) ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Self-hosted2Prerequisites3Install Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Prerequisites Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ### Cluster config mapping examples[​](#cluster-config-mapping-examples "Direct link to Cluster config mapping examples") The `clusterConfMapping` parameter is crucial for connecting to your Kafka clusters. The cluster configuration mapping should be a JSON object with cluster names as keys and Kafka client configurations as values. Each client configuration follows the standard Kafka client properties format. Below are some examples of how to configure the `clusterConfMapping` parameter for different Kafka cluster configurations. **Basic SASL\_SSL authentication** For clusters with SASL authentication over SSL: **Basic SASL\_SSL authentication (click to expand)** * Helm/ArgoCD * CI/CD Environment Variable ``` integration: secrets: clusterConfMapping: | { "production-cluster": { "bootstrap.servers": "broker1:9092,broker2:9092,broker3:9092", "security.protocol": "SASL_SSL", "sasl.mechanism": "SCRAM-SHA-256", "sasl.username": "your-username", "sasl.password": "your-password" }, "staging-cluster": { "bootstrap.servers": "staging-broker1:9092,staging-broker2:9092", "security.protocol": "SASL_SSL", "sasl.mechanism": "SCRAM-SHA-256", "sasl.username": "staging-username", "sasl.password": "staging-password" } } ``` ``` export OCEAN__INTEGRATION__CONFIG__CLUSTER_CONF_MAPPING='{ "production-cluster": { "bootstrap.servers": "broker1:9092,broker2:9092,broker3:9092", "security.protocol": "SASL_SSL", "sasl.mechanism": "SCRAM-SHA-256", "sasl.username": "your-username", "sasl.password": "your-password" }, "staging-cluster": { "bootstrap.servers": "staging-broker1:9092,staging-broker2:9092", "security.protocol": "SASL_SSL", "sasl.mechanism": "SCRAM-SHA-256", "sasl.username": "staging-username", "sasl.password": "staging-password" } }' ``` **Plain text authentication** For clusters without SSL encryption: **Plain text authentication (click to expand)** * Helm/ArgoCD * CI/CD Environment Variable ``` integration: secrets: clusterConfMapping: | { "internal-cluster": { "bootstrap.servers": "internal-broker1:9092,internal-broker2:9092", "security.protocol": "SASL_PLAINTEXT", "sasl.mechanism": "PLAIN", "sasl.username": "internal-username", "sasl.password": "internal-password" } } ``` ``` export OCEAN__INTEGRATION__CONFIG__CLUSTER_CONF_MAPPING='{ "internal-cluster": { "bootstrap.servers": "internal-broker1:9092,internal-broker2:9092", "security.protocol": "SASL_PLAINTEXT", "sasl.mechanism": "PLAIN", "sasl.username": "internal-username", "sasl.password": "internal-password" } }' ``` **mTLS authentication** For clusters with mutual TLS authentication: **mTLS authentication (click to expand)** * Helm/ArgoCD * CI/CD Environment Variable ``` integration: secrets: clusterConfMapping: | { "secure-cluster": { "bootstrap.servers": "secure-broker1:9092,secure-broker2:9092", "security.protocol": "SSL", "ssl.ca.location": "/path/to/ca-cert.pem", "ssl.certificate.location": "/path/to/client-cert.pem", "ssl.key.location": "/path/to/client-key.pem", "ssl.key.password": "key-password" } } ``` ``` export OCEAN__INTEGRATION__CONFIG__CLUSTER_CONF_MAPPING='{ "secure-cluster": { "bootstrap.servers": "secure-broker1:9092,secure-broker2:9092", "security.protocol": "SSL", "ssl.ca.location": "/path/to/ca-cert.pem", "ssl.certificate.location": "/path/to/client-cert.pem", "ssl.key.location": "/path/to/client-key.pem", "ssl.key.password": "key-password" } }' ``` **Confluent cloud configuration** For Confluent Cloud clusters: **Confluent cloud configuration (click to expand)** * Helm/ArgoCD * CI/CD Environment Variable ``` integration: secrets: clusterConfMapping: | { "confluent-cloud-cluster": { "bootstrap.servers": "pkc-abcd85.us-west-2.aws.confluent.cloud:9092", "security.protocol": "SASL_SSL", "sasl.mechanism": "PLAIN", "sasl.username": "your-api-key", "sasl.password": "your-api-secret" } } ``` ``` export OCEAN__INTEGRATION__CONFIG__CLUSTER_CONF_MAPPING='{ "confluent-cloud-cluster": { "bootstrap.servers": "pkc-abcd85.us-west-2.aws.confluent.cloud:9092", "security.protocol": "SASL_SSL", "sasl.mechanism": "PLAIN", "sasl.username": "your-api-key", "sasl.password": "your-api-secret" } }' ``` **Advanced configuration with custom properties** For clusters requiring additional custom properties: **Advanced configuration with custom properties (click to expand)** * Helm/ArgoCD * CI/CD Environment Variable ``` integration: secrets: clusterConfMapping: | { "advanced-cluster": { "bootstrap.servers": "broker1:9092,broker2:9092,broker3:9092", "security.protocol": "SASL_SSL", "sasl.mechanism": "SCRAM-SHA-512", "sasl.username": "advanced-username", "sasl.password": "advanced-password", "ssl.ca.location": "/path/to/ca-cert.pem", "ssl.endpoint.identification.algorithm": "https", "request.timeout.ms": 30000, "session.timeout.ms": 10000, "enable.auto.commit": false } } ``` ``` export OCEAN__INTEGRATION__CONFIG__CLUSTER_CONF_MAPPING='{ "advanced-cluster": { "bootstrap.servers": "broker1:9092,broker2:9092,broker3:9092", "security.protocol": "SASL_SSL", "sasl.mechanism": "SCRAM-SHA-512", "sasl.username": "advanced-username", "sasl.password": "advanced-password", "ssl.ca.location": "/path/to/ca-cert.pem", "ssl.endpoint.identification.algorithm": "https", "request.timeout.ms": 30000, "session.timeout.ms": 10000, "enable.auto.commit": false } }' ``` You can configure multiple clusters in the same mapping by adding additional cluster configurations as shown in the examples above. Each cluster will be processed independently by the integration. Security considerations * Store sensitive information like passwords and API keys securely using your platform's secret management system. * For Kubernetes deployments, use Kubernetes secrets to store the `clusterConfMapping` value. * For CI/CD pipelines, use environment variable encryption features provided by your CI/CD platform. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` resources: - kind: cluster selector: query: 'true' port: entity: mappings: identifier: .name title: .name blueprint: '"kafkaCluster"' properties: controllerId: .controller_id - kind: broker selector: query: 'true' port: entity: mappings: identifier: .cluster_name + "_" + (.id | tostring) title: .cluster_name + " " + (.id | tostring) blueprint: '"kafkaBroker"' properties: address: .address region: .config."broker.rack" version: .config."inter.broker.protocol.version" config: .config relations: cluster: .cluster_name - kind: topic selector: query: 'true' port: entity: mappings: identifier: .cluster_name + "_" + .name title: .cluster_name + " " + .name blueprint: '"kafkaTopic"' properties: replicas: .partitions[0].replicas | length partitions: .partitions | length compaction: .config."cleanup.policy" | contains("compact") retention: .config."cleanup.policy" | contains("delete") deleteRetentionTime: .config."delete.retention.ms" partitionsMetadata: .partitions config: .config relations: cluster: .cluster_name brokers: '[.cluster_name + "_" + (.partitions[].replicas[] | tostring)] | unique' - kind: consumer_group selector: query: 'true' port: entity: mappings: identifier: .cluster_name + "_" + .group_id title: .group_id blueprint: '"kafkaConsumerGroup"' properties: state: .state members: '[.members[].client_id]' coordinator: .coordinator.id partition_assignor: .partition_assignor is_simple_consumer_group: .is_simple_consumer_group authorized_operations: .authorized_operations relations: cluster: .cluster_name ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Examples of blueprints and the relevant integration configurations: ClusterBrokerTopicConsumer Group BlueprintJSON Create in Port ``` { "identifier": "kafkaCluster", "title": "Cluster", "icon": "Kafka", "schema": { "properties": { "controllerId": { "title": "Controller ID", "type": "string" } } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: false deleteDependentEntities: true resources: - kind: cluster selector: query: "true" port: entity: mappings: identifier: .name title: .name blueprint: '"kafkaCluster"' properties: controllerId: .controller_id ``` BlueprintJSON Create in Port ``` { "identifier": "kafkaBroker", "title": "Broker", "icon": "Kafka", "schema": { "properties": { "address": { "title": "Address", "type": "string" }, "region": { "title": "Region", "type": "string" }, "version": { "title": "Version", "type": "string" }, "config": { "title": "Config", "type": "object" } } }, "relations": { "cluster": { "target": "kafkaCluster", "required": true, "many": false } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: false deleteDependentEntities: true resources: - kind: broker selector: query: "true" port: entity: mappings: identifier: .cluster_name + "_" + (.id | tostring) title: .cluster_name + " " + (.id | tostring) blueprint: '"kafkaBroker"' properties: address: .address region: .config."broker.rack" version: .config."inter.broker.protocol.version" config: .config relations: cluster: .cluster_name ``` BlueprintJSON Create in Port ``` { "identifier": "kafkaTopic", "title": "Topic", "icon": "Kafka", "schema": { "properties": { "replicas": { "title": "Replicas", "type": "number" }, "partitions": { "title": "Partitions", "type": "number" }, "compaction": { "title": "Compaction", "type": "boolean" }, "retention": { "title": "Retention", "type": "boolean" }, "deleteRetentionTime": { "title": "Delete Retention Time", "type": "number" }, "partitionsMetadata": { "title": "Partitions Metadata", "type": "array" }, "config": { "title": "Config", "type": "object" } } }, "relations": { "cluster": { "target": "kafkaCluster", "required": true, "many": false }, "brokers": { "target": "kafkaBroker", "required": false, "many": true } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: false deleteDependentEntities: true resources: - kind: topic selector: query: "true" port: entity: mappings: identifier: .cluster_name + "_" + .name title: .cluster_name + " " + .name blueprint: '"kafkaTopic"' properties: replicas: .partitions[0].replicas | length partitions: .partitions | length compaction: .config."cleanup.policy" | contains("compact") retention: .config."cleanup.policy" | contains("delete") deleteRetentionTime: .config."delete.retention.ms" partitionsMetadata: .partitions config: .config relations: cluster: .cluster_name brokers: '[.cluster_name + "_" + (.partitions[].replicas[] | tostring)] | unique' ``` BlueprintJSON Create in Port ``` { "identifier": "kafkaConsumerGroup", "title": "Consumer Group", "icon": "Kafka", "schema": { "properties": { "state": { "title": "State", "type": "string", "description": "The current state of the consumer group." }, "members": { "title": "Members", "type": "array", "description": "List of members in the consumer group.", "items": { "type": "string" } }, "coordinator": { "title": "Coordinator", "type": "number", "description": "Broker ID of the coordinator for the consumer group." }, "partition_assignor": { "title": "Partition Assignor", "type": "string", "description": "Strategy used to assign partitions to consumers." }, "is_simple_consumer_group": { "title": "Is Simple Consumer Group", "type": "boolean", "description": "Indicates if the group is a simple consumer group." }, "authorized_operations": { "title": "Authorized Operations", "type": "array", "description": "List of operations authorized for the consumer group.", "items": { "type": "string" } } } }, "calculationProperties": {}, "relations": { "cluster": { "target": "kafkaCluster", "required": true, "many": false } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: false deleteDependentEntities: true resources: - kind: consumer_group selector: query: 'true' port: entity: mappings: identifier: .cluster_name + "_" + .group_id title: .group_id blueprint: '"kafkaConsumerGroup"' properties: state: .state members: .members | map(.client_id) coordinator: .coordinator.id partition_assignor: .partition_assignor is_simple_consumer_group: .is_simple_consumer_group authorized_operations: .authorized_operations relations: cluster: .cluster_name ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # LaunchDarkly ![Launchdarkly icon](/img/guides/icons/Launchdarkly.svg)![Launchdarkly icon](/img/guides/icons/dark/Launchdarkly.svg) Loading version... Port's LaunchDarkly integration allows you to model LaunchDarkly resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://launchdarkly.com/docs/api/projects)[Audit Log](https://launchdarkly.com/docs/api/audit-log)[Flag](https://launchdarkly.com/docs/api/feature-flags)[Environment](https://launchdarkly.com/docs/api/environments)[Flag Status](https://launchdarkly.com/docs/api/feature-flags/get-feature-flag-status-across-environments) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to 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: '"launchDarklyProject"' properties: tags: .tags - kind: flag selector: query: 'true' port: entity: mappings: identifier: .key + "-" + .__projectKey title: .name blueprint: '"launchDarklyFeatureFlag"' properties: kind: .kind description: .description creationDate: .creationDate / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ") clientSideAvailability: .clientSideAvailability temporary: .temporary tags: .tags maintainer: ._maintainer.email deprecated: .deprecated variations: .variations customProperties: .customProperties archived: .archived relations: project: .__projectKey - kind: environment selector: query: 'true' port: entity: mappings: identifier: .key + "-" + .__projectKey title: .name blueprint: '"launchDarklyEnvironment"' properties: defaultTtl: .defaultTtl secureMode: .secureMode defaultTrackEvents: .defaultTrackEvents requireComments: .requireComments confirmChanges: .confirmChanges tags: .tags critical: .critical relations: project: .__projectKey - kind: flag-status selector: query: 'true' port: entity: mappings: identifier: . as $root | ._links.self.href | split("/") | last as $last | "\($last)-\($root.__environmentKey)" title: . as $root | ._links.self.href | split("/") | last as $last | "\($last)-\($root.__environmentKey)" blueprint: '"launchDarklyFFInEnvironment"' properties: status: .name relations: environment: .__environmentKey + "-" + .__projectKey featureFlag: . as $input | $input._links.self.href | split("/") | .[-1] + "-" + $input.__projectKey ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to 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 [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ProjectFlagEnvironmentFlag Status Default mappingYAML ``` - kind: project selector: query: 'true' port: entity: mappings: identifier: .key title: .name blueprint: '"launchDarklyProject"' properties: tags: .tags ``` Sample payloadJSON ``` { "_links": { "environments": { "href": "/api/v2/projects/fourth-project/environments", "type": "application/json" }, "flagDefaults": { "href": "/api/v2/projects/fourth-project/flag-defaults", "type": "application/json" }, "self": { "href": "/api/v2/projects/fourth-project", "type": "application/json" } }, "_id": "666b298cc671e81012b578c6", "key": "fourth-project", "includeInSnippetByDefault": false, "defaultClientSideAvailability": { "usingMobileKey": false, "usingEnvironmentId": false }, "name": "Fourth Project", "tags": [] } ``` Port entityJSON ``` { "identifier": "fourth-project", "title": "Fourth Project", "blueprint": "launchDarklyProject", "properties": { "tags": [] }, "relation": { "service": "fourth-project" } } ``` Default mappingYAML ``` - kind: flag selector: query: 'true' port: entity: mappings: identifier: .key + "-" + .__projectKey title: .name blueprint: '"launchDarklyFeatureFlag"' properties: kind: .kind description: .description creationDate: .creationDate / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ") clientSideAvailability: .clientSideAvailability temporary: .temporary tags: .tags maintainer: ._maintainer.email deprecated: .deprecated variations: .variations customProperties: .customProperties archived: .archived relations: project: .__projectKey ``` Sample payloadJSON ``` { "_links": { "parent": { "href": "/api/v2/flags/fourth-project", "type": "application/json" }, "self": { "href": "/api/v2/flags/fourth-project/randomflag", "type": "application/json" } }, "_maintainer": { "_id": "6669b0f34162860fefd6d724", "_links": { "self": { "href": "/api/v2/members/6669b0f34162860fefd6d724", "type": "application/json" } }, "email": "example@gmail.com", "firstName": "John", "lastName": "Doe", "role": "owner" }, "_version": 1, "archived": false, "clientSideAvailability": { "usingEnvironmentId": false, "usingMobileKey": false }, "creationDate": 1718299647527, "customProperties": {}, "defaults": { "offVariation": 1, "onVariation": 0 }, "deprecated": false, "description": "", "environments": { "fourth-env": { "_environmentName": "fourth-env", "_site": { "href": "/fourth-project/fourth-env/features/randomflag", "type": "text/html" }, "_summary": { "prerequisites": 0, "variations": { "0": { "contextTargets": 0, "isFallthrough": true, "nullRules": 0, "rules": 0, "targets": 0 }, "1": { "contextTargets": 0, "isOff": true, "nullRules": 0, "rules": 0, "targets": 0 } } }, "archived": false, "lastModified": 1718299647539, "on": false, "salt": "c713989066a446febf07a42d488221e8", "sel": "6d7c3692dd9d4ffa8eee8e2d96b6fd2c", "trackEvents": false, "trackEventsFallthrough": false, "version": 1 }, "new-env": { "_environmentName": "new env", "_site": { "href": "/fourth-project/new-env/features/randomflag", "type": "text/html" }, "_summary": { "prerequisites": 0, "variations": { "0": { "contextTargets": 0, "isFallthrough": true, "nullRules": 0, "rules": 0, "targets": 0 }, "1": { "contextTargets": 0, "isOff": true, "nullRules": 0, "rules": 0, "targets": 0 } } }, "archived": false, "lastModified": 1718299647539, "on": false, "salt": "caa436a38411406491f0da9230349bb3", "sel": "8bcf1667ab2f4f628fc26ad31966f045", "trackEvents": false, "trackEventsFallthrough": false, "version": 1 }, "new-project": { "_environmentName": "new_project", "_site": { "href": "/fourth-project/new-project/features/randomflag", "type": "text/html" }, "_summary": { "prerequisites": 0, "variations": { "0": { "contextTargets": 0, "isFallthrough": true, "nullRules": 0, "rules": 0, "targets": 0 }, "1": { "contextTargets": 0, "isOff": true, "nullRules": 0, "rules": 0, "targets": 0 } } }, "archived": false, "lastModified": 1718299647539, "on": false, "salt": "f79c8849d22d497d8a519fbb6263aeda", "sel": "257f0acaf18f4252b40258f8aa93b966", "trackEvents": false, "trackEventsFallthrough": false, "version": 1 }, "production": { "_environmentName": "Production", "_site": { "href": "/fourth-project/production/features/randomflag", "type": "text/html" }, "_summary": { "prerequisites": 0, "variations": { "0": { "contextTargets": 0, "isFallthrough": true, "nullRules": 0, "rules": 0, "targets": 0 }, "1": { "contextTargets": 0, "isOff": true, "nullRules": 0, "rules": 0, "targets": 0 } } }, "archived": false, "lastModified": 1718299647539, "on": false, "salt": "28c5efba5fd445d5896a8b9f7f8fbff6", "sel": "28a317cdf3aa4d40b8a0b1c6f56be4c9", "trackEvents": false, "trackEventsFallthrough": false, "version": 1 }, "shadow": { "_environmentName": "shadow", "_site": { "href": "/fourth-project/shadow/features/randomflag", "type": "text/html" }, "_summary": { "prerequisites": 0, "variations": { "0": { "contextTargets": 0, "isFallthrough": true, "nullRules": 0, "rules": 0, "targets": 0 }, "1": { "contextTargets": 0, "isOff": true, "nullRules": 0, "rules": 0, "targets": 0 } } }, "archived": false, "lastModified": 1718311480830, "on": false, "salt": "cb214aeac84f48d08ff136514c589b11", "sel": "00b5f9ae56a547db9c4e5e619bdb39f3", "trackEvents": false, "trackEventsFallthrough": false, "version": 1 }, "some-random-env": { "_environmentName": "some-random-env", "_site": { "href": "/fourth-project/some-random-env/features/randomflag", "type": "text/html" }, "_summary": { "prerequisites": 0, "variations": { "0": { "contextTargets": 0, "isFallthrough": true, "nullRules": 0, "rules": 0, "targets": 0 }, "1": { "contextTargets": 0, "isOff": true, "nullRules": 0, "rules": 0, "targets": 0 } } }, "archived": false, "lastModified": 1718300514123, "on": false, "salt": "0618861de85c48a5a77c360db7a8847b", "sel": "5ae511fe5630469084453c2c4d45f719", "trackEvents": false, "trackEventsFallthrough": false, "version": 1 }, "staging": { "_environmentName": "staging", "_site": { "href": "/fourth-project/staging/features/randomflag", "type": "text/html" }, "_summary": { "prerequisites": 0, "variations": { "0": { "contextTargets": 0, "isFallthrough": true, "nullRules": 0, "rules": 0, "targets": 0 }, "1": { "contextTargets": 0, "isOff": true, "nullRules": 0, "rules": 0, "targets": 0 } } }, "archived": false, "lastModified": 1718300902420, "on": false, "salt": "bc27ddc205984379a4863f5f1323bdb0", "sel": "2762811a62734de79277544ff4362f8c", "trackEvents": false, "trackEventsFallthrough": false, "version": 1 }, "test": { "_environmentName": "Test", "_site": { "href": "/fourth-project/test/features/randomflag", "type": "text/html" }, "_summary": { "prerequisites": 0, "variations": { "0": { "contextTargets": 0, "isFallthrough": true, "nullRules": 0, "rules": 0, "targets": 0 }, "1": { "contextTargets": 0, "isOff": true, "nullRules": 0, "rules": 0, "targets": 0 } } }, "archived": false, "lastModified": 1718299647539, "on": false, "salt": "fac0fe470f844433986166f3d570415d", "sel": "8e8ae9542dc94f35b1ac64c845277d8a", "trackEvents": false, "trackEventsFallthrough": false, "version": 1 } }, "experiments": { "baselineIdx": 0, "items": [] }, "goalIds": [], "includeInSnippet": false, "key": "randomflag", "kind": "boolean", "maintainerId": "6669b0f34162860fefd6d724", "name": "randomflag", "tags": [], "temporary": true, "variationJsonSchema": null, "variations": [ { "_id": "8868f0d9-8b1d-4575-9436-827188276792", "value": true }, { "_id": "8929317b-d2aa-479c-9249-e6c0ec5dc415", "value": false } ], "__projectKey": "fourth-project" } ``` Port entityJSON ``` { "identifier": "randomflag-fourth-project", "title": "randomflag", "blueprint": "launchDarklyFeatureFlag", "properties": { "kind": "boolean", "description": "", "creationDate": "2024-06-13T17:27:27Z", "clientSideAvailability": { "usingEnvironmentId": false, "usingMobileKey": false }, "temporary": true, "tags": [], "maintainer": "example@gmail.com", "deprecated": false, "variations": [ { "_id": "8868f0d9-8b1d-4575-9436-827188276792", "value": true }, { "_id": "8929317b-d2aa-479c-9249-e6c0ec5dc415", "value": false } ], "customProperties": {}, "archived": false }, "relations": { "project": "fourth-project" } } ``` Default mappingYAML ``` - kind: environment selector: query: 'true' port: entity: mappings: identifier: .key + "-" + .__projectKey title: .name blueprint: '"launchDarklyEnvironment"' properties: defaultTtl: .defaultTtl secureMode: .secureMode defaultTrackEvents: .defaultTrackEvents requireComments: .requireComments confirmChanges: .confirmChanges tags: .tags critical: .critical relations: project: .__projectKey ``` Sample payloadJSON ``` { "_links": { "analytics": { "href": "https://app.launchdarkly.com/snippet/events/v1/666b2a74cbdbfb108f3fc911.js", "type": "text/html" }, "apiKey": { "href": "/api/v2/projects/fourth-project/environments/fourth-env/apiKey", "type": "application/json" }, "mobileKey": { "href": "/api/v2/projects/fourth-project/environments/fourth-env/mobileKey", "type": "application/json" }, "self": { "href": "/api/v2/projects/fourth-project/environments/fourth-env", "type": "application/json" }, "snippet": { "href": "https://app.launchdarkly.com/snippet/features/666b2a74cbdbfb108f3fc911.js", "type": "text/html" } }, "_id": "666b2a74cbdbfb108f3fc911", "_pubnub": { "channel": "b4f644c56dbbfe88a4028cb2d2142c258926f9b7a9add263d105202f0cd6599c", "cipherKey": "9571e2de187881614fe9b6b94d13a99fbdb056e508c9226e6c6bb7d0be117725" }, "key": "fourth-env", "name": "fourth-env", "apiKey": "sdk-1b3cf928-acae-4553-aab3-c956b7f04219", "mobileKey": "mob-87679d8a-698d-4c5f-9ec1-05e368975afe", "color": "e2e6ff", "defaultTtl": 0, "secureMode": false, "defaultTrackEvents": false, "requireComments": false, "confirmChanges": false, "tags": [], "approvalSettings": { "required": false, "bypassApprovalsForPendingChanges": false, "minNumApprovals": 1, "canReviewOwnRequest": false, "canApplyDeclinedChanges": true, "serviceKind": "launchdarkly", "serviceConfig": {}, "requiredApprovalTags": [] }, "critical": false, "__projectKey": "fourth-project" } ``` Port entityJSON ``` { "identifier": "fourth-env-fourth-project", "title": "fourth-env", "blueprint": "launchDarklyEnvironment", "properties": { "defaultTtl": 0, "secureMode": false, "defaultTrackEvents": false, "requireComments": false, "confirmChanges": false, "tags": [], "critical": false }, "relations": { "project": "fourth-project" } } ``` Default mappingYAML ``` - kind: flag-status selector: query: 'true' port: entity: mappings: identifier: . as $root | ._links.self.href | split("/") | last as $last | "\($last)-\($root.__environmentKey)" title: . as $root | ._links.self.href | split("/") | last as $last | "\($last)-\($root.__environmentKey)" blueprint: '"launchDarklyFFInEnvironment"' properties: status: .name relations: environment: .__environmentKey + "-" + .__projectKey featureFlag: . as $input | $input._links.self.href | split("/") | .[-1] + "-" + $input.__projectKey ``` Sample payloadJSON ``` { "_links": { "parent": { "href": "/api/v2/flags/fourth-project/olulufe", "type": "application/json" }, "self": { "href": "/api/v2/flag-statuses/fourth-project/shadow/olulufe", "type": "application/json" } }, "name": "new", "lastRequested": null, "__environmentKey": "shadow", "__projectKey": "fourth-project" } ``` Port entityJSON ``` { "identifier": "olulufe-shadow", "title": "olulufe-shadow", "blueprint": "launchDarklyFFInEnvironment", "properties": { "status": "new" }, "relations": { "environment": "shadow-fourth-project", "featureFlag": "olulufe-fourth-project" } } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 LaunchDarkly 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 LaunchDarkly 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If LaunchDarkly is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that LaunchDarkly can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=Launchdarkly). --- # Git Port's Git integrations make it easy to keep your software catalog up to date based on information stored in your Git provider. In addition, our integrations make it possible to manage the software catalog using GitOps. For more information, check out the documentation for your respective provider: ## [🗃GitHub (Ocean)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md) [4 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md) ## [🗃GitHub (Sunset)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) [5 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) ## [🗃Bitbucket](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/.md) [3 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/.md) ## [🗃GitLab](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md) [6 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md) ## [🗃GitLab (deprecated)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md) [5 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md) ## [🗃Azure DevOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md) [5 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md) ## [📄️Working with monorepos](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/working-with-monorepos/.md) [Learn how to configure Git integrations to create separate entities for each microservice in a monorepo.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/working-with-monorepos/.md) ## [📄️GitOps using Port CRDs](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitops-using-port-crd/.md) [Learn how to use GitOps with Port's Kubernetes CRDs to export custom entities from your K8s clusters to the catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitops-using-port-crd/.md) --- # Azure DevOps ![AzureDevops icon](/img/guides/icons/AzureDevops.svg)![AzureDevops icon](/img/guides/icons/AzureDevops.svg) Loading version... Port's Azure DevOps integration allows you to model Azure DevOps resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[repository](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/repositories/list?view=azure-devops-rest-7.2\&tabs=HTTP#gitrepository)[branch](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/refs/list?view=azure-devops-rest-7.1#gitref)[repository-policy](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/policy-configurations/get?view=azure-devops-rest-7.1#policyconfiguration)[project](https://learn.microsoft.com/en-us/rest/api/azure/devops/core/projects/list?view=azure-devops-rest-7.1\&tabs=HTTP#teamprojectreference)[pull-request](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-requests/get-pull-requests?view=azure-devops-rest-7.1\&tabs=HTTP#gitpullrequest)[pipeline](https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/pipelines/list?view=azure-devops-rest-7.2#pipeline)[build](https://learn.microsoft.com/en-us/rest/api/azure/devops/build/builds/list?view=azure-devops-rest-7.1\&tabs=HTTP#build)[pipeline-stage](https://learn.microsoft.com/en-us/rest/api/azure/devops/build/timeline/get?view=azure-devops-rest-7.1)[pipeline-run](https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/runs/list?view=azure-devops-rest-7.1\&tabs=HTTP#run)[user](https://learn.microsoft.com/en-us/rest/api/azure/devops/memberentitlementmanagement/user-entitlements/list?view=azure-devops-rest-4.1\&tabs=HTTP)[work-item](https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/wiql/query-by-wiql?view=azure-devops-rest-7.1\&tabs=HTTP)[area-path](https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/classification-nodes/get-classification-nodes)[board](https://learn.microsoft.com/en-us/rest/api/azure/devops/work/boards/list?view=azure-devops-rest-7.1)[column](https://learn.microsoft.com/en-us/rest/api/azure/devops/work/columns/list?view=azure-devops-rest-7.1)[release](https://learn.microsoft.com/en-us/rest/api/azure/devops/release/releases?view=azure-devops-rest-7.1)[release-definition](https://learn.microsoft.com/en-us/rest/api/azure/devops/release/definitions/list?view=azure-devops-rest-7.1)[environment](https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/environments/list?view=azure-devops-rest-7.1\&tabs=HTTP)[release-deployment](https://learn.microsoft.com/en-us/rest/api/azure/devops/release/deployments/list?view=azure-devops-rest-7.1\&tabs=HTTP)[pipeline-deployment](https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/environmentdeployment-records/list?view=azure-devops-rest-7.1)[file](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/items/get-items-batch?view=azure-devops-rest-7.1\&tabs=HTTP)[folder](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/items/get?view=azure-devops-rest-7.1\&tabs=HTTP)[advanced-security-alert](https://learn.microsoft.com/en-us/rest/api/azure/devops/advancedsecurity/alerts/list?view=azure-devops-rest-7.2\&tabs=HTTP)[group](https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/groups/list?view=azure-devops-rest-7.1\&tabs=HTTP)[group-member](https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/list?view=azure-devops-rest-7.1\&tabs=HTTP)[wiki](https://learn.microsoft.com/en-us/rest/api/azure/devops/wiki/wikis/list?view=azure-devops-rest-7.1)[team](https://learn.microsoft.com/en-us/rest/api/azure/devops/core/teams/get-all-teams?view=azure-devops-rest-7.1)[test-run](https://learn.microsoft.com/en-us/rest/api/azure/devops/test/runs/list?view=azure-devops-rest-7.1)[member](https://learn.microsoft.com/en-us/rest/api/azure/devops/core/teams/get-team-members-with-extended-properties?view=azure-devops-rest-7.1)[iteration](https://learn.microsoft.com/en-us/rest/api/azure/devops/work/iterations/list?view=azure-devops-rest-7.1) ## Core support [Incremental syncClosed betaSee supported resources](#incremental-sync)[Live eventsSee supported resources](#set-up-live-events) Supported deployments models This integration supports both **Azure DevOps Services (cloud)** and **Azure DevOps Server (on-premises)**. **Note:** When using the on-premises model, `releases` and `work items` are not supported due to API version differences. All other supported resources work the same as the cloud version. **Service principal authentication** (via Microsoft Entra ID) is available for **Azure DevOps Services (cloud)** and works for both single-account and multi-account installations. Azure DevOps Server (on-premises) must continue to use a personal access token. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * Single Account * Multiple Accounts - An Azure DevOps account with admin privileges. - If you choose the self-hosted installation method, you will need a Kubernetes cluster on which to install the integration. - Your Port user role is set to `Admin`. Backwards compatibility Existing installations that authenticate with a personal access token and a single organization URL continue to work without any changes. When `accountMode` is not set, the integration defaults to `Single Account` mode. ### Authentication methods The integration supports two authentication methods. Choose the one that matches your deployment: * **Personal access token (PAT)**: Authenticates against a single Azure DevOps organization (or a single on-premises collection). This is required for Azure DevOps Server (on-premises). * **Service principal** (recommended): Uses a [Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity-platform/app-objects-and-service-principals) application to authenticate with your Azure DevOps organization. This is available for Azure DevOps Services (cloud). Personal access tokens are being retired Microsoft is retiring global personal access tokens in Azure DevOps on **December 1, 2026**, after which existing global PATs will stop working. For Azure DevOps Services (cloud), we recommend migrating to **service principal** authentication, which uses short-lived, Microsoft Entra-backed credentials. See Microsoft's announcement: [Retirement of global personal access tokens in Azure DevOps](https://devblogs.microsoft.com/devops/retirement-of-global-personal-access-tokens-in-azure-devops/). * Service principal (recommended) * Personal access token Version requirement Service principal authentication requires Azure DevOps integration version **0.9.0** or later. We will create a Microsoft Entra ID application, grant it access to each Azure DevOps organization you want to sync, and provide the resulting credentials to the integration. The same flow applies whether you sync a single organization or multiple organizations from one installation. 1. **Register an application in Microsoft Entra ID.** Follow [Register an application with the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). After registration, record the **Application (client) ID** and the **Directory (tenant) ID** from the app's **Overview** page. 2. **Create a client secret.** On the app registration, go to **Certificates & secrets** and create a new client secret. Copy the **secret value** immediately (it will not be shown again). 3. **Add the service principal to each Azure DevOps organization** you want to sync. Follow [Use service principals and managed identities in Azure DevOps](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops). For every target organization: * Open the organization at `https://dev.azure.com/{organization}`. * Navigate to **Organization settings** > **Users** > **Add users**. * Search for the Enterprise Application's display name (the same name as the app registration you created). * Assign an access level of **Basic** or higher. **Stakeholder** is not sufficient for repository access. 4. **Grant the service principal permission on each project or repository** you want to sync. Use the same Azure DevOps permission model you would apply to a user (for example, **Project Settings** > **Permissions** or **Repository Settings** > **Security**). 5. **Collect the organization URLs** you want to sync. Each URL must follow the format `https://dev.azure.com/{organization}`. For example: ``` https://dev.azure.com/my-org https://dev.azure.com/another-org ``` You will pass these as a list to the integration. **Required permissions (click to expand)** * Azure DevOps does **not** use Microsoft Entra ID application permissions. All access control is enforced through the Azure DevOps permission system, so the app registration itself does not need any API permissions configured. See [the Microsoft docs](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops) for the full explanation. * Permissions must be granted separately **inside each Azure DevOps organization** you sync, using that organization's permission model. * Each organization counts as a separate license for the service principal (no multi-organization licensing discount). * The service principal must belong to the **same Microsoft Entra tenant** that each target Azure DevOps organization is connected to. Cross-tenant access requires Microsoft's documented key-vault and certificate workaround. Sync time grows with organizations When syncing multiple organizations, Port processes a few at a time in parallel rather than all at once. Azure DevOps also enforces its own API rate limits **separately for each organization**. As a result, the more organizations you sync, the longer a full resync takes. Once complete, you will have: * A list of **organization URLs** (for example, `https://dev.azure.com/org-a` and `https://dev.azure.com/org-b`). * A **tenant ID**. * A **client ID**. * A **client secret value**. Provide these four values to the integration using the installation method you chose below. The integration requires a personal access token to authenticate with your Azure DevOps account. Follow [Microsoft's PAT guide](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops\&tabs=Windows#create-a-pat) to create one. The token should either have `admin` permissions, or `read` permissions for each of the supported resources you want to ingest into Port. Personal access tokens are scoped to a single organization (or a single on-premises collection). To sync multiple organizations from one installation, switch to the **Multiple Accounts** tab above. * An Azure DevOps account with admin privileges in **each organization** you want to sync. * If you choose the self-hosted installation method, you will need a Kubernetes cluster on which to install the integration. * Your Port user role is set to `Admin`. * All target Azure DevOps organizations must be backed by the **same Microsoft Entra tenant** as the service principal. ### Authentication Multi-account installations require **service principal** authentication. Personal access tokens are scoped to a single organization, so they cannot be used in multi-account mode. Version requirement Service principal authentication requires Azure DevOps integration version **0.9.0** or later. We will create a Microsoft Entra ID application, grant it access to each Azure DevOps organization you want to sync, and provide the resulting credentials to the integration. The same flow applies whether you sync a single organization or multiple organizations from one installation. 1. **Register an application in Microsoft Entra ID.** Follow [Register an application with the Microsoft identity platform](https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app). After registration, record the **Application (client) ID** and the **Directory (tenant) ID** from the app's **Overview** page. 2. **Create a client secret.** On the app registration, go to **Certificates & secrets** and create a new client secret. Copy the **secret value** immediately (it will not be shown again). 3. **Add the service principal to each Azure DevOps organization** you want to sync. Follow [Use service principals and managed identities in Azure DevOps](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops). For every target organization: * Open the organization at `https://dev.azure.com/{organization}`. * Navigate to **Organization settings** > **Users** > **Add users**. * Search for the Enterprise Application's display name (the same name as the app registration you created). * Assign an access level of **Basic** or higher. **Stakeholder** is not sufficient for repository access. 4. **Grant the service principal permission on each project or repository** you want to sync. Use the same Azure DevOps permission model you would apply to a user (for example, **Project Settings** > **Permissions** or **Repository Settings** > **Security**). 5. **Collect the organization URLs** you want to sync. Each URL must follow the format `https://dev.azure.com/{organization}`. For example: ``` https://dev.azure.com/my-org https://dev.azure.com/another-org ``` You will pass these as a list to the integration. **Required permissions (click to expand)** * Azure DevOps does **not** use Microsoft Entra ID application permissions. All access control is enforced through the Azure DevOps permission system, so the app registration itself does not need any API permissions configured. See [the Microsoft docs](https://learn.microsoft.com/en-us/azure/devops/integrate/get-started/authentication/service-principal-managed-identity?view=azure-devops) for the full explanation. * Permissions must be granted separately **inside each Azure DevOps organization** you sync, using that organization's permission model. * Each organization counts as a separate license for the service principal (no multi-organization licensing discount). * The service principal must belong to the **same Microsoft Entra tenant** that each target Azure DevOps organization is connected to. Cross-tenant access requires Microsoft's documented key-vault and certificate workaround. Sync time grows with organizations When syncing multiple organizations, Port processes a few at a time in parallel rather than all at once. Azure DevOps also enforces its own API rate limits **separately for each organization**. As a result, the more organizations you sync, the longer a full resync takes. Once complete, you will have: * A list of **organization URLs** (for example, `https://dev.azure.com/org-a` and `https://dev.azure.com/org-b`). * A **tenant ID**. * A **client ID**. * A **client secret value**. Provide these four values to the integration using the installation method you chose below. ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). * Single Account * Multiple Accounts 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .id title: .name blueprint: '"azureDevopsRepository"' properties: url: .remoteUrl readme: .__includedFiles["README.md"] id: .id last_activity: .project.lastUpdateTime relations: project: .project.id | gsub(" "; "") - kind: repository-policy selector: query: .type.displayName=="Minimum number of reviewers" port: entity: mappings: identifier: .__repository.id blueprint: '"azureDevopsRepository"' properties: minimumApproverCount: .settings.minimumApproverCount - kind: repository-policy selector: query: .type.displayName=="Work item linking" port: entity: mappings: identifier: .__repository.id blueprint: '"azureDevopsRepository"' properties: workItemLinking: .isEnabled and .isBlocking - kind: user selector: query: 'true' port: entity: mappings: identifier: .id title: .user.displayName blueprint: '"azureDevopsMember"' properties: url: .user.url email: .user.mailAddress - kind: pull-request selector: query: 'true' port: entity: mappings: identifier: .repository.id + "/" + (.pullRequestId | tostring) blueprint: '"azureDevopsPullRequest"' properties: status: .status createdAt: .creationDate leadTimeHours: (.creationDate as $createdAt | .status as $status | .closedDate as $closedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($closedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $closedTimestamp | if $status == "completed" and $closedTimestamp != null then (((($closedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) else null end) relations: repository: .repository.id service: combinator: '"and"' rules: - operator: '"="' property: '"ado_repository_id"' value: .repository.id creator: combinator: '"and"' rules: - operator: '"="' property: '"$identifier"' value: .createdBy.uniqueName reviewers: combinator: '"and"' rules: - operator: '"in"' property: '"$identifier"' value: '[.reviewers[].uniqueName]' azure_devops_reviewers: '[.reviewers[].id]' azure_devops_creator: .createdBy.id - kind: build selector: query: "true" port: entity: mappings: identifier: .__project.id + "/" + (.id | tostring) | gsub(" "; "") title: .buildNumber blueprint: '"azureDevopsBuild"' properties: status: .status result: .result queueTime: .queueTime startTime: .startTime finishTime: .finishTime definitionName: .definition.name requestedFor: .requestedFor.displayName link: ._links.web.href relations: pipeline: .__project.id + "/" + (.definition.id | tostring) - kind: pipeline-stage selector: query: 'true' port: entity: mappings: identifier: .__project.id + "/" + (.__build.id | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsPipelineStage"' properties: state: .state result: .result startTime: .startTime finishTime: .finishTime stageType: .type relations: build: (.__project.id + "/" + (.__build.id | tostring)) | gsub(" "; "") pipelineRun: .__project.id + "/" + (.__build.id | tostring) | gsub(" "; "") - kind: pipeline-run selector: query: 'true' port: entity: mappings: identifier: .__project.id + "/" + (.__pipeline.id | tostring) + "/" + (.id | tostring) | gsub(" "; "") blueprint: '"azureDevopsPipelineRun"' properties: state: .state result: .result createdDate: .createdDate finishedDate: .finishedDate pipelineName: .pipeline.name relations: pipeline: .__project.id + "/" + (.__pipeline.id | tostring) - kind: environment selector: query: 'true' port: entity: mappings: identifier: .project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name | tostring blueprint: '"azureDevopsEnvironment"' properties: description: .description createdOn: .createdOn lastModifiedOn: .lastModifiedOn relations: {} - kind: release-deployment selector: query: 'true' includeRelease: true port: entity: mappings: identifier: .releaseDefinition.projectReference.id + "/" + (.release.id | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .release.name + "-" + (.id | tostring) | gsub(" "; "") blueprint: '"azureDevopsReleaseDeployment"' properties: status: .deploymentStatus url: .url reason: .reason startedOn: .startedOn completedOn: .completedOn requestedBy: .requestedBy.displayName operationStatus: .operationStatus environment: .releaseEnvironment.name relations: release: .releaseDefinition.projectReference.id + "/" + (.release.id | tostring) | gsub(" "; "") - kind: pipeline-deployment selector: query: 'true' port: entity: mappings: identifier: .__project.id + "/" + (.environmentId | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .requestIdentifier | tostring blueprint: '"azureDevopsPipelineDeployment"' properties: planType: .planType stageName: .stageName jobName: .jobName result: .result startTime: .startTime finishTime: .finishTime relations: environment: .__project.id + "/" + (.environmentId | tostring) | gsub(" "; "") build: .__project.id + "/" + (.owner.id | tostring) | gsub(" "; "") ``` ### Multi-organization mapping[​](#multi-organization-mapping "Direct link to Multi-organization mapping") When the integration runs in multi-organization mode, every ingested entity is enriched with two extra fields that you can reference in JQ: * `__organizationUrl`: the full URL of the Azure DevOps organization the entity came from (for example, `https://dev.azure.com/org-a`). * `__organizationName`: the short organization name extracted from that URL (for example, `org-a`). Use these fields to disambiguate entities that share an ID across organizations, or to surface the source organization on each entity. **Example multi-organization mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' port: entity: mappings: identifier: .__organizationName + "/" + (.id | gsub(" "; "")) blueprint: '"azureDevopsProject"' title: .__organizationName + " / " + .name properties: organization: .__organizationName organizationUrl: .__organizationUrl state: .state visibility: .visibility - kind: repository selector: query: 'true' port: entity: mappings: identifier: .__organizationName + "/" + .id title: .__organizationName + " / " + .name blueprint: '"azureDevopsRepository"' properties: url: .remoteUrl organization: .__organizationName organizationUrl: .__organizationUrl relations: project: .__organizationName + "/" + (.project.id | gsub(" "; "")) ``` The default mapping above continues to work for single-organization installations without any changes. Add the snippet above only if you use the service principal authentication method and sync multiple organizations. ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/examples/.md). ProjectRepositoryWork ItemArea PathPipelinePull RequestBuildPipeline StagePipeline RunBranchAdvanced Security AlertRelease DefinitionRepository PolicyUserBoardColumnReleaseEnvironmentRelease DeploymentPipeline DeploymentFileGroupGroup Member Default mappingYAML ``` - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") ``` Sample payloadJSON ``` { "id": "fd029361-7854-4cdd-8ace-bb033fca399c", "name": "Port Integration", "description": "Ocean integration project", "url": "[REDACTED]/_apis/projects/fd029361-7854-4cdd-8ace-bb033fca399c", "state": "wellFormed", "revision": 21, "_links": { "self": { "href": "[REDACTED]/_apis/projects/fd029361-7854-4cdd-8ace-bb033fca399c" }, "collection": { "href": "[REDACTED]/_apis/projectCollections/a7db27e5-15a1-4e84-aca5-3de8874e5466" }, "web": { "href": "[REDACTED]/Port Integration" } }, "visibility": "private", "defaultTeam": { "id": "da84d6cf-fc6f-4a3a-b9f1-eccaf320589c", "name": "Port Integration Team", "url": "[REDACTED]/_apis/projects/fd029361-7854-4cdd-8ace-bb033fca399c/teams/da84d6cf-fc6f-4a3a-b9f1-eccaf320589c" }, "lastUpdateTime": "2023-11-14T07:24:17.213Z" } ``` Port entityJSON ``` { "identifier": "fd029361-7854-4cdd-8ace-bb033fca399c", "title": "Port Integration", "blueprint": "azureDevopsProject", "properties": { "state": "wellFormed", "revision": 21, "visibility": "private", "defaultTeam": "Port Integration Team", "link": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Default mappingYAML ``` - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .id title: .name blueprint: '"azureDevopsRepository"' properties: url: .remoteUrl readme: .__includedFiles["README.md"] id: .id last_activity: .project.lastUpdateTime relations: project: .project.id | gsub(" "; "") ``` Sample payloadJSON ``` { "id": "43c319c8-5adc-41f8-8486-745fe2130cd6", "name": "final_project_to_project_test", "url": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_apis/git/repositories/43c319c8-5adc-41f8-8486-745fe2130cd6", "project": { "id": "fd029361-7854-4cdd-8ace-bb033fca399c", "name": "Port Integration", "description": "Ocean integration project", "url": "[REDACTED]/_apis/projects/fd029361-7854-4cdd-8ace-bb033fca399c", "state": "wellFormed", "revision": 21, "visibility": "private", "lastUpdateTime": "2023-11-14T07:24:17.213Z" }, "defaultBranch": "refs/heads/main", "size": 724, "remoteUrl": "https://isaacpcoffie@dev.azure.com/isaacpcoffie/Port%20Integration/_git/final_project_to_project_test", "sshUrl": "git@ssh.dev.azure.com:v3/isaacpcoffie/Port%20Integration/final_project_to_project_test", "webUrl": "[REDACTED]/Port%20Integration/_git/final_project_to_project_test", "isDisabled": false, "isInMaintenance": false } ``` Port entityJSON ``` { "identifier": "43c319c8-5adc-41f8-8486-745fe2130cd6", "title": "final_project_to_project_test", "blueprint": "azureDevopsRepository", "properties": { "url": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_apis/git/repositories/43c319c8-5adc-41f8-8486-745fe2130cd6", "readme": "", "defaultBranch": "refs/heads/main" }, "relations": { "project": "fd029361-7854-4cdd-8ace-bb033fca399c" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `includedFiles` | `array` | List of file paths to fetch from the repository and attach to the raw data under \_\_includedFiles. E.g. \['README.md', 'CODEOWNERS'] | Default mappingYAML ``` - kind: work-item selector: query: 'true' expand: 'All' wiql: "[System.State] NOT IN ('Closed','Done','Removed') OR [System.ChangedDate] >= @today - 7" port: entity: mappings: identifier: .id | tostring title: .fields."System.Title" blueprint: '"azureDevopsWorkItem"' properties: type: .fields."System.WorkItemType" state: .fields."System.State" effort: .fields."Microsoft.VSTS.Scheduling.Effort" description: .fields."System.Description" link: .url reason: .fields."System.Reason" createdBy: .fields."System.CreatedBy".displayName changedBy: .fields."System.ChangedBy".displayName assignedTo: .fields."System.AssignedTo".displayName createdDate: .fields."System.CreatedDate" changedDate: .fields."System.ChangedDate" relations: project: .__projectId | gsub(" "; "") column: >- .fields."System.WorkItemType" + "/" + .fields."System.BoardColumn" + "/" + .__project.id | gsub(" "; "") areaPath: combinator: '"and"' rules: - property: '"normalizedPath"' operator: '"="' value: .fields."System.AreaPath" ``` Sample payloadJSON ``` { "id": 1, "rev": 2, "fields": { "System.AreaPath": "Test Project", "System.TeamProject": "Test Project", "System.IterationPath": "Test Project\\Sprint 1", "System.WorkItemType": "Issue", "System.State": "To Do", "System.Reason": "Added to backlog", "System.AssignedTo": { "displayName": "Jaden Kodjo Miles", "url": "https://spsprodcus5.vssps.visualstudio.com/Ac557ed0f-d9a1-4fab-b2fb-95d2f2493d42/_apis/Identities/40bee502-30c1-6eb5-9750-f9d35fa66e6f", "_links": { "avatar": { "href": "[REDACTED]/_apis/GraphProfile/MemberAvatars/msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" } }, "id": "40bee502-30c1-6eb5-9750-f9d35fa66e6f", "uniqueName": "doe@gmail.com", "imageUrl": "[REDACTED]/_apis/GraphProfile/MemberAvatars/msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm", "descriptor": "msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" }, "System.CreatedDate": "2023-11-14T06:58:16.353Z", "System.CreatedBy": { "displayName": "Jaden Kodjo Miles", "url": "https://spsprodcus5.vssps.visualstudio.com/Ac557ed0f-d9a1-4fab-b2fb-95d2f2493d42/_apis/Identities/40bee502-30c1-6eb5-9750-f9d35fa66e6f", "_links": { "avatar": { "href": "[REDACTED]/_apis/GraphProfile/MemberAvatars/msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" } }, "id": "40bee502-30c1-6eb5-9750-f9d35fa66e6f", "uniqueName": "doe@gmail.com", "imageUrl": "[REDACTED]/_apis/GraphProfile/MemberAvatars/msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm", "descriptor": "msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" }, "System.ChangedDate": "2023-11-14T06:58:32.69Z", "System.ChangedBy": { "displayName": "Jaden Kodjo Miles", "url": "https://spsprodcus5.vssps.visualstudio.com/Ac557ed0f-d9a1-4fab-b2fb-95d2f2493d42/_apis/Identities/40bee502-30c1-6eb5-9750-f9d35fa66e6f", "_links": { "avatar": { "href": "[REDACTED]/_apis/GraphProfile/MemberAvatars/msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" } }, "id": "40bee502-30c1-6eb5-9750-f9d35fa66e6f", "uniqueName": "doe@gmail.com", "imageUrl": "[REDACTED]/_apis/GraphProfile/MemberAvatars/msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm", "descriptor": "msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" }, "System.CommentCount": 0, "System.Title": "setup backend infra", "System.BoardColumn": "To Do", "System.BoardColumnDone": false, "Microsoft.VSTS.Common.StateChangeDate": "2023-11-14T06:58:16.353Z", "Microsoft.VSTS.Common.Priority": 2, "WEF_88F4173AE02645C58988F456A7D828AB_Kanban.Column": "To Do", "WEF_88F4173AE02645C58988F456A7D828AB_Kanban.Column.Done": false }, "url": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/wit/workItems/1", "__projectId": "1b6aba50-6176-4df2-a8e3-f0394ec0b0a2", "__project": { "id": "1b6aba50-6176-4df2-a8e3-f0394ec0b0a2", "name": "Test Project", "description": "This is a project for Port", "url": "[REDACTED]/_apis/projects/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2", "state": "wellFormed", "revision": 13, "visibility": "private", "lastUpdateTime": "2023-11-14T06:56:02.157Z" } } ``` Port entityJSON ``` { "identifier": "1", "title": "setup backend infra", "blueprint": "azureDevopsWorkItem", "properties": { "type": "Issue", "state": "To Do", "effort": null, "description": null, "link": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/wit/workItems/1", "reason": "Added to backlog", "createdBy": "Jaden Kodjo Miles", "changedBy": "Jaden Kodjo Miles", "createdDate": "2023-11-14T06:58:16.353Z", "changedDate": "2023-11-14T06:58:32.69Z" }, "relations": { "project": "1b6aba50-6176-4df2-a8e3-f0394ec0b0a2", "areaPath": "a0e5d8b1-2c3d-4e4f-8b7a-1d0c9f6e5b2a" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `wiql` | `string` | WIQL query to filter work items. If not provided, all work items will be fetched. | | `expand` | `string` | Expand options for work items. Allowed values are 'None', 'Fields', 'Relations', 'Links' and 'All'. Default value is 'All'. Default: `"All"`. | | `updatedSince` | `string` | Only include work items changed on or after this date (ISO 8601; date precision only). Ignored during incremental sync. | Default mappingYAML ``` - kind: area-path selector: query: 'true' port: entity: mappings: identifier: .identifier title: .name blueprint: '"azureDevopsAreaPath"' properties: name: .name path: .path normalizedPath: .__normalizedPath hasChildren: .hasChildren relations: project: .__projectId | gsub(" "; "") parent: .__parentIdentifier ``` Sample payloadJSON ``` { "id": 126, "identifier": "b1f6a9c2-3d4e-4f5a-9c8b-2e1d0a7f6c3b", "name": "Backend", "structureType": "area", "hasChildren": false, "path": "\\Test Project\\Area\\Backend", "attributes": null, "url": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/wit/classificationNodes/Areas/Backend", "_links": { "self": { "href": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/wit/classificationNodes/Areas/Backend" }, "parent": { "href": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/wit/classificationNodes/Areas" } }, "__projectId": "1b6aba50-6176-4df2-a8e3-f0394ec0b0a2", "__normalizedPath": "Test Project\\Backend", "__parentIdentifier": "a0e5d8b1-2c3d-4e4f-8b7a-1d0c9f6e5b2a" } ``` Port entityJSON ``` { "identifier": "b1f6a9c2-3d4e-4f5a-9c8b-2e1d0a7f6c3b", "title": "Backend", "blueprint": "azureDevopsAreaPath", "properties": { "name": "Backend", "path": "\\Test Project\\Area\\Backend", "normalizedPath": "Test Project\\Backend", "hasChildren": false }, "relations": { "project": "1b6aba50-6176-4df2-a8e3-f0394ec0b0a2", "parent": "a0e5d8b1-2c3d-4e4f-8b7a-1d0c9f6e5b2a" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `depth` | `integer` | How many levels of the area-path tree to fetch in a single call. | Default mappingYAML ``` - kind: pipeline selector: query: 'true' port: entity: mappings: identifier: .__projectId + "/" + (.id | tostring) title: .name blueprint: '"azureDevopsPipeline"' properties: url: .url revision: .revision folder: .folder relations: project: '.__projectId | gsub(" "; "")' ``` Sample payloadJSON ``` { "_links": { "self": { "href": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_apis/pipelines/7?revision=1" }, "web": { "href": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_build/definition?definitionId=7" } }, "url": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_apis/pipelines/7?revision=1", "id": 7, "revision": 1, "name": "health-catalist", "folder": "\\", "__projectId": "fd029361-7854-4cdd-8ace-bb033fca399c" } ``` Port entityJSON ``` { "identifier": "fd029361-7854-4cdd-8ace-bb033fca399c/7", "title": "health-catalist", "blueprint": "azureDevopsPipeline", "properties": { "url": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_apis/pipelines/7?revision=1", "revision": 1, "folder": "\\" }, "relations": { "repository": "a1b2c3d4-5678-90ab-cdef-1234567890ab" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `includeRepo` | `boolean` | Whether to include the repository for each pipeline, defaults to false Default: `false`. | Default mappingYAML ``` - kind: pull-request selector: query: 'true' port: entity: mappings: identifier: .repository.id + "/" + (.pullRequestId | tostring) blueprint: '"azureDevopsPullRequest"' properties: status: .status createdAt: .creationDate leadTimeHours: (.creationDate as $createdAt | .status as $status | .closedDate as $closedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($closedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $closedTimestamp | if $status == "completed" and $closedTimestamp != null then (((($closedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) else null end) relations: repository: .repository.id service: combinator: '"and"' rules: - operator: '"="' property: '"ado_repository_id"' value: .repository.id creator: combinator: '"and"' rules: - operator: '"="' property: '"$identifier"' value: .createdBy.uniqueName reviewers: combinator: '"and"' rules: - operator: '"in"' property: '"$identifier"' value: '[.reviewers[].uniqueName]' azure_devops_reviewers: '[.reviewers[].id]' azure_devops_creator: .createdBy.id ``` Sample payloadJSON ``` { "repository": { "id": "075e1870-9a1a-4e3d-a219-6403c2004298", "name": "data-analysis", "url": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/git/repositories/075e1870-9a1a-4e3d-a219-6403c2004298", "project": { "id": "1b6aba50-6176-4df2-a8e3-f0394ec0b0a2", "name": "Test Project", "state": "unchanged", "visibility": "unchanged", "lastUpdateTime": "0001-01-01T00:00:00" } }, "pullRequestId": 1, "codeReviewId": 1, "status": "active", "createdBy": { "displayName": "Jaden Kodjo Miles", "url": "https://spsprodcus5.vssps.visualstudio.com/Ac557ed0f-d9a1-4fab-b2fb-95d2f2493d42/_apis/Identities/40bee502-30c1-6eb5-9750-f9d35fa66e6f", "_links": { "avatar": { "href": "[REDACTED]/_apis/GraphProfile/MemberAvatars/msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" } }, "id": "40bee502-30c1-6eb5-9750-f9d35fa66e6f", "uniqueName": "doe@gmail.com", "imageUrl": "[REDACTED]/_api/_common/identityImage?id=40bee502-30c1-6eb5-9750-f9d35fa66e6f", "descriptor": "msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" }, "creationDate": "2023-11-14T06:53:58.355547Z", "title": "First Pull Request", "description": "some description", "sourceRefName": "refs/heads/master", "targetRefName": "refs/heads/main", "mergeStatus": "conflicts", "isDraft": false, "mergeId": "6c00586e-ebda-40a3-a09b-66454e4c352d", "lastMergeSourceCommit": { "commitId": "00ce7ec80fd8ce6cde516432dc9aadf190d5c977", "url": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/git/repositories/075e1870-9a1a-4e3d-a219-6403c2004298/commits/00ce7ec80fd8ce6cde516432dc9aadf190d5c977" }, "lastMergeTargetCommit": { "commitId": "a5c15f13af7d5f97369163fd76a63502600ada55", "url": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/git/repositories/075e1870-9a1a-4e3d-a219-6403c2004298/commits/a5c15f13af7d5f97369163fd76a63502600ada55" }, "reviewers": [ { "reviewerUrl": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/git/repositories/075e1870-9a1a-4e3d-a219-6403c2004298/pullRequests/1/reviewers/40bee502-30c1-6eb5-9750-f9d35fa66e6f", "vote": 10, "hasDeclined": false, "isFlagged": false, "displayName": "Jaden Kodjo Miles", "url": "https://spsprodcus5.vssps.visualstudio.com/Ac557ed0f-d9a1-4fab-b2fb-95d2f2493d42/_apis/Identities/40bee502-30c1-6eb5-9750-f9d35fa66e6f", "_links": { "avatar": { "href": "[REDACTED]/_apis/GraphProfile/MemberAvatars/msa.NDBiZWU1MDItMzBjMS03ZWI1LTk3NTAtZjlkMzVmYTY2ZTZm" } }, "id": "40bee502-30c1-6eb5-9750-f9d35fa66e6f", "uniqueName": "doe@gmail.com", "imageUrl": "[REDACTED]/_api/_common/identityImage?id=40bee502-30c1-6eb5-9750-f9d35fa66e6f" } ], "labels": [ { "id": "68e1d7ae-1784-49fe-8865-2742c25b1993", "name": "bitbucket", "active": true }, { "id": "b11e1538-a984-440c-b756-ddc72e0e786c", "name": "auth", "active": true } ], "url": "[REDACTED]/1b6aba50-6176-4df2-a8e3-f0394ec0b0a2/_apis/git/repositories/075e1870-9a1a-4e3d-a219-6403c2004298/pullRequests/1", "supportsIterations": true } ``` Port entityJSON ``` { "identifier": "075e1870-9a1a-4e3d-a219-6403c2004298/1", "blueprint": "azureDevopsPullRequest", "properties": { "status": "active", "createdAt": "2023-11-14T06:53:58.355547Z", "leadTimeHours": null }, "relations": { "repository": "075e1870-9a1a-4e3d-a219-6403c2004298", "azure_devops_creator": "40bee502-30c1-6eb5-9750-f9d35fa66e6f", "azure_devops_reviewers": ["40bee502-30c1-6eb5-9750-f9d35fa66e6f"] } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `minTimeInDays` | `integer` | Minimum time in days since the pull request was abandoned or closed. Default: `7`. | | `maxResults` | `integer` | Maximum number of closed pull requests to fetch. Default: `100`. | Default mappingYAML ``` - kind: build selector: query: '.finishTime != null' port: entity: mappings: identifier: .__project.id + "/" + (.id | tostring) | gsub(" "; "") title: .buildNumber blueprint: '"azureDevopsBuild"' properties: status: .status result: .result queueTime: .queueTime startTime: .startTime finishTime: .finishTime definitionName: .definition.name requestedFor: .requestedFor.displayName sourceBranch: .sourceBranch | gsub("refs/heads/"; "") link: ._links.web.href relations: pipeline: .__project.id + "/" + (.definition.id | tostring) ``` Sample payloadJSON ``` { "_links": { "self": { "href": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_apis/build/builds/123" }, "web": { "href": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_build/results?buildId=123" } }, "id": 123, "buildNumber": "20231114.1", "status": "completed", "result": "succeeded", "queueTime": "2023-11-14T07:00:00.000Z", "startTime": "2023-11-14T07:00:15.000Z", "finishTime": "2023-11-14T07:05:30.000Z", "definition": { "id": 7, "name": "health-catalist", "path": "\\" }, "requestedFor": { "displayName": "Jaden Kodjo Miles", "id": "40bee502-30c1-6eb5-9750-f9d35fa66e6f", "uniqueName": "doe@gmail.com" }, "__project": { "id": "fd029361-7854-4cdd-8ace-bb033fca399c", "name": "Port Integration" } } ``` Port entityJSON ``` { "identifier": "fd029361-7854-4cdd-8ace-bb033fca399c/123", "title": "20231114.1", "blueprint": "azureDevopsBuild", "properties": { "status": "completed", "result": "succeeded", "queueTime": "2023-11-14T07:00:00.000Z", "startTime": "2023-11-14T07:00:15.000Z", "finishTime": "2023-11-14T07:05:30.000Z", "definitionName": "health-catalist", "requestedFor": "Jaden Kodjo Miles", "link": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_build/results?buildId=123" }, "relations": { "pipeline": "fd029361-7854-4cdd-8ace-bb033fca399c/7" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ----------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | | `enrichWithFirstCommit` | `boolean` | When enabled, each build is enriched under \_\_firstCommit with the earliest commit it shipped (\_\_sha, \_\_timestamp in UTC, \_\_commitCount). Defaults to false. Default: `false`. | | `updatedSince` | `string` | Only include builds queued after this date (ISO 8601). Ignored during incremental sync. | Default mappingYAML ``` - kind: pipeline-stage selector: query: '.finishTime != null' port: entity: mappings: identifier: .__project.id + "/" + (.__build.id | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsPipelineStage"' properties: state: .state result: .result startTime: .startTime finishTime: .finishTime stageType: .type relations: build: .__project.id + "/" + (.__build.id | tostring) | gsub(" "; "") pipelineRun: .__project.id + "/" + (.__build.id | tostring) | gsub(" "; "") ``` Sample payloadJSON ``` { "id": 1, "name": "Build", "type": "build", "state": "completed", "result": "succeeded", "startTime": "2023-11-14T07:00:15.000Z", "finishTime": "2023-11-14T07:02:45.000Z", "__project": { "id": "fd029361-7854-4cdd-8ace-bb033fca399c", "name": "Port Integration" }, "__buildId": 123 } ``` Port entityJSON ``` { "identifier": "fd029361-7854-4cdd-8ace-bb033fca399c/123/1", "title": "Build", "blueprint": "azureDevopsPipelineStage", "properties": { "state": "completed", "result": "succeeded", "startTime": "2023-11-14T07:00:15.000Z", "finishTime": "2023-11-14T07:02:45.000Z", "stageType": "build" }, "relations": { "build": "fd029361-7854-4cdd-8ace-bb033fca399c/123", "pipelineRun": "fd029361-7854-4cdd-8ace-bb033fca399c/456" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Default mappingYAML ``` - kind: pipeline-run selector: query: '.finishedDate != null' port: entity: mappings: identifier: .__project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsPipelineRun"' properties: state: .state result: .result createdDate: .createdDate finishedDate: .finishedDate pipelineName: .pipeline.name relations: pipeline: .__project.id + "/" + (.__pipeline.id | tostring) | gsub(" "; "") ``` Sample payloadJSON ``` { "id": 456, "state": "completed", "result": "succeeded", "createdDate": "2023-11-14T07:00:00.000Z", "finishedDate": "2023-11-14T07:05:30.000Z", "pipeline": { "id": 7, "name": "health-catalist" }, "__project": { "id": "fd029361-7854-4cdd-8ace-bb033fca399c", "name": "Port Integration" }, "__pipeline": { "id": 7 } } ``` Port entityJSON ``` { "identifier": "fd029361-7854-4cdd-8ace-bb033fca399c/7/456", "blueprint": "azureDevopsPipelineRun", "properties": { "state": "completed", "result": "succeeded", "createdDate": "2023-11-14T07:00:00.000Z", "finishedDate": "2023-11-14T07:05:30.000Z", "pipelineName": "health-catalist" }, "relations": { "pipeline": "fd029361-7854-4cdd-8ace-bb033fca399c/7" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Default mappingYAML ``` - kind: branch selector: query: 'true' port: entity: mappings: identifier: .objectId title: .name blueprint: '"azureDevopsBranch"' properties: repositoryName: .__repository.name projectName: .__repository.project.name link: .__repository.webUrl + "?version=GB" + .name relations: repository: .__repository.id ``` Sample payloadJSON ``` { "name": "refs/heads/feature/new-feature", "objectId": "ffe9cba521f00d7f60e322845072238635edb451", "creator": { "displayName": "Normal Paulk", "url": "https://vssps.dev.azure.com/fabrikam/_apis/Identities/ac5aaba6-a66a-4e1d-b508-b060ec624fa9", "_links": { "avatar": { "href": "https://dev.azure.com/fabrikam/_apis/GraphProfile/MemberAvatars/aad.YmFjMGYyZDctNDA3ZC03OGRhLTlhMjUtNmJhZjUwMWFjY2U5" } }, "id": "ac5aaba6-a66a-4e1d-b508-b060ec624fa9", "uniqueName": "dev@mailserver.com", "imageUrl": "https://dev.azure.com/fabrikam/_api/_common/identityImage?id=ac5aaba6-a66a-4e1d-b508-b060ec624fa9", "descriptor": "aad.YmFjMGYyZDctNDA3ZC03OGRhLTlhMjUtNmJhZjUwMWFjY2U5" }, "url": "https://dev.azure.com/fabrikam/7484f783-66a3-4f27-b7cd-6b08b0b077ed/_apis/git/repositories/d3d1760b-311c-4175-a726-20dfc6a7f885/refs?filter=heads%2Ffeature%2Fnew-feature", "__repository": { "id": "d3d1760b-311c-4175-a726-20dfc6a7f885", "name": "my-repository", "url": "https://dev.azure.com/fabrikam/7484f783-66a3-4f27-b7cd-6b08b0b077ed/_apis/git/repositories/d3d1760b-311c-4175-a726-20dfc6a7f885", "webUrl": "https://dev.azure.com/fabrikam/7484f783-66a3-4f27-b7cd-6b08b0b077ed/_git/my-repository", "project": { "id": "7484f783-66a3-4f27-b7cd-6b08b0b077ed", "name": "My Project", "url": "https://dev.azure.com/fabrikam/_apis/projects/7484f783-66a3-4f27-b7cd-6b08b0b077ed", "state": "wellFormed", "revision": 12, "visibility": "public", "lastUpdateTime": "2025-05-04T09:34:21.397Z" } } } ``` Port entityJSON ``` { "identifier": "ffe9cba521f00d7f60e322845072238635edb451", "title": "refs/heads/feature/new-feature", "blueprint": "azureDevopsBranch", "properties": { "repositoryName": "my-repository", "projectName": "My Project", "link": "https://dev.azure.com/fabrikam/7484f783-66a3-4f27-b7cd-6b08b0b077ed/_git/my-repository?version=GBrefs/heads/feature/new-feature" }, "relations": { "repository": "d3d1760b-311c-4175-a726-20dfc6a7f885" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Default mappingYAML ``` - kind: advanced-security-alert selector: query: 'true' port: entity: mappings: identifier: .__repositoryId + "/" + (.alertId | tostring) title: .title blueprint: '"azureDevopsAdvancedSecurity"' properties: severity: .severity state: .state alertType: .alertType introducedDate: .introducedDate confidence: .confidence link: .physicalLocations[0].versionControl.itemUrl relations: repository: .__repositoryId project: .__projectId ``` Sample payloadJSON ``` { "alertId": 2, "severity": "critical", "title": "Google API key", "tools": [ { "name": "Advanced Security Secrets Scanning", "rules": [ { "opaqueId": "SEC101/003", "friendlyName": "GoogleApiKey", "description": "Do not expose plaintext (or base64-encoded plaintext) secrets in versioned engineering content.", "resources": "", "helpMessage": "## Remediation steps\n\nFollow the steps below before you close this alert:\n\n1. Rotate the secret and store securely if it's in use to prevent breaking workflows.\n2. Revoke this Google API key to prevent unauthorized access.\n3. Check security logs for potential breaches.\n4. Close the alert as revoked.", "tags": null, "additionalProperties": {} } ] } ], "dismissal": null, "repositoryId": null, "projectId": "00000000-0000-0000-0000-000000000000", "repositoryUrl": "[REDACTED]/ac5aaba6-a66a-4e1d-b508-b060ec624fa9/_git/864e1b4d-d163-47f5-87fb-22f2d4046fa6", "gitRef": null, "alertType": "secret", "firstSeenDate": "2026-01-23T01:35:26.9669502Z", "lastSeenDate": "2026-01-23T01:36:28.0573831Z", "fixedDate": null, "introducedDate": "2023-06-08T23:25:14Z", "state": "active", "physicalLocations": [ { "versionControl": { "commitHash": "b78974cefc96712e72081e66c18a1d71f1948ccd", "itemUrl": "https://dev.azure.com:443/fabrikam/ac5aaba6-a66a-4e1d-b508-b060ec624fa9/_git/864e1b4d-d163-47f5-87fb-22f2d4046fa6?path=%2Fios%2FRunner%2FGoogleService-Info.plist&version=GCb78974cefc96712e72081e66c18a1d71f1948ccd&line=10&lineEnd=10&lineStartColumn=10&lineEndColumn=49&lineStyle=plain" }, "filePath": "ios/Runner/GoogleService-Info.plist", "region": { "lineStart": 10, "lineEnd": 10, "columnStart": 10, "columnEnd": 49 } } ], "logicalLocations": [], "truncatedSecret": "…iMF-XM", "confidence": "high", "hasTrustedSourceOrigin": true, "__repositoryId": "864e1b4d-d163-47f5-87fb-22f2d4046fa6", "__projectId": "ac5aaba6-a66a-4e1d-b508-b060ec624fa9" } ``` Port entityJSON ``` { "blueprint": "azureDevopsAdvancedSecurity", "identifier": "864e1b4d-d163-47f5-87fb-22f2d4046fa6/2", "title": "Google API key", "properties": { "severity": "critical", "alertType": "secret", "introducedDate": "2023-06-08T23:25:14Z", "state": "active", "confidence": "high", "link": "https://dev.azure.com:443/fabrikam/ac5aaba6-a66a-4e1d-b508-b060ec624fa9/_git/864e1b4d-d163-47f5-87fb-22f2d4046fa6?path=%2Fios%2FRunner%2FGoogleService-Info.plist&version=GCb78974cefc96712e72081e66c18a1d71f1948ccd&line=10&lineEnd=10&lineStartColumn=10&lineEndColumn=49&lineStyle=plain" }, "relations": { "project": "ac5aaba6-a66a-4e1d-b508-b060ec624fa9", "repository": "864e1b4d-d163-47f5-87fb-22f2d4046fa6" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `criteria` | `object` | Filter criteria for alerts. If not provided, all alerts will be fetched. | `criteria` parameters | Parameter | Type | Description | | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `states` | `array` | List of states to filter alerts by. If not provided, all states will be fetched. Allowed values: `active`, `dismissed`, `fixed`, `autoDismissed`. | | `severity` | `array` | List of severity levels to filter alerts by. If not provided, all severity levels will be fetched. Allowed values: `low`, `medium`, `high`, `critical`, `note`, `warning`, `error`. | | `alertType` | `string` | Type of alerts to filter by. If not provided, all alerts will be fetched. Allowed values: `dependency`, `code`, `secret`. | | `updatedSince` | `string` | Only include alerts modified after this date (ISO 8601). Ignored during incremental sync. | Default mappingYAML ``` - kind: release-definition selector: query: 'true' port: entity: mappings: identifier: .__project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsReleaseDefinition"' properties: description: .description createdDate: .createdOn modifiedDate: .modifiedOn createdBy: .createdBy.uniqueName modifiedBy: .modifiedBy.uniqueName releaseNameFormat: .releaseNameFormat path: .path link: ._links.web.href relations: project: .__project.id | gsub(" "; "") ``` Sample payloadJSON ``` { "source": "userInterface", "revision": 3, "description": "Deploy to production environments", "createdBy": { "displayName": "John Doe", "url": "[REDACTED]/_apis/Identities/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "uniqueName": "john.doe@example.com" }, "createdOn": "2025-01-15T10:30:00.000Z", "modifiedBy": { "displayName": "Jane Smith", "url": "[REDACTED]/_apis/Identities/b2c3d4e5-f6a7-8901-bcde-f12345678901", "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "uniqueName": "jane.smith@example.com" }, "modifiedOn": "2025-03-20T14:45:00.000Z", "isDeleted": false, "variableGroups": [], "releaseNameFormat": "Release-$(rev:r)", "id": 2, "name": "Production Pipeline", "path": "\\", "projectReference": null, "url": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_apis/Release/definitions/2", "_links": { "self": { "href": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_apis/Release/definitions/2" }, "web": { "href": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_release?definitionId=2" } }, "__project": { "id": "fd029361-7854-4cdd-8ace-bb033fca399c", "name": "MyProject" } } ``` Port entityJSON ``` { "identifier": "fd029361-7854-4cdd-8ace-bb033fca399c/2", "title": "Production Pipeline", "blueprint": "azureDevopsReleaseDefinition", "properties": { "description": "Deploy to production environments", "createdDate": "2025-01-15T10:30:00.000Z", "modifiedDate": "2025-03-20T14:45:00.000Z", "createdBy": "john.doe@example.com", "modifiedBy": "jane.smith@example.com", "releaseNameFormat": "Release-$(rev:r)", "path": "\\", "link": "[REDACTED]/fd029361-7854-4cdd-8ace-bb033fca399c/_release?definitionId=2" }, "relations": { "project": "fd029361-7854-4cdd-8ace-bb033fca399c" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `expand` | `string` | Property to expand on each release definition. Defaults to 'environments' which provides stage information. Default: `"environments"`. | | `tagFilter` | `array` | List of tags. Only release definitions with these tags will be returned. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'true' port: entity: mappings: identifier: '.id | gsub(" "; "")' blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .id title: .name blueprint: '"azureDevopsRepository"' properties: url: .remoteUrl readme: .__includedFiles["README.md"] relations: project: .project.id | gsub(" "; "") - kind: repository-policy selector: query: .type.displayName=="Minimum number of reviewers" port: entity: mappings: identifier: >- "\(.__repository.project.name | ascii_downcase | gsub("[ ();]"; ""))/\(.__repository.name | ascii_downcase | gsub("[ ();]"; ""))" blueprint: '"azureDevopsRepository"' properties: minimumApproverCount: .settings.minimumApproverCount - kind: repository-policy selector: query: .type.displayName=="Work item linking" port: entity: mappings: identifier: >- "\(.__repository.project.name | ascii_downcase | gsub("[ ();]"; ""))/\(.__repository.name | ascii_downcase | gsub("[ ();]"; ""))" blueprint: '"azureDevopsRepository"' properties: workItemLinking: .isEnabled and .isBlocking - kind: pull-request selector: query: 'true' port: entity: mappings: identifier: .repository.id + "/" + (.pullRequestId | tostring) blueprint: '"azureDevopsPullRequest"' properties: status: .status createdAt: .creationDate leadTimeHours: >- (.creationDate as $createdAt | .status as $status | .closedDate as $closedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($closedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $closedTimestamp | if $status == "completed" and $closedTimestamp != null then (((($closedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) else null end) relations: repository: .repository.id creator: .createdBy.uniqueName reviewers: '[.reviewers[].uniqueName]' ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: user selector: query: 'true' port: entity: mappings: identifier: .user.mailAddress title: .user.displayName blueprint: '"azureDevopsUser"' properties: url: .user.url - kind: project selector: query: 'true' defaultTeam: 'true' port: entity: mappings: identifier: '.id | gsub(" "; "")' blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `source` | `string` | Where to read users from. 'graph' uses the read-only Graph Users API (vso.graph scope); 'entitlements' uses the Member Entitlement Management API, which also returns license data but requires an elevated scope. Default: `"entitlements"`. | | `subjectTypes` | `array` | Graph source only. Filter users by subject subtype, e.g. 'aad', 'msa', 'svc', 'imp'. | | `includeGroupMemberships` | `boolean` | Graph source only. Enrich each user with their direct group memberships (\_\_groups) and derived project entitlements (\_\_projects). Default: `false`. | | `includeFields` | `array` | Entitlements source only. List of additional properties to include in user entitlements. | | `apiVersion` | `string` | Entitlements source only. API version for the User Entitlements endpoint. Override if your organization requires a specific version. Will use the default version if not provided. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: board selector: query: 'true' port: entity: mappings: identifier: .id | gsub(" "; "") title: .name blueprint: '"azureDevopsBoard"' properties: link: .url relations: project: .__project.id | gsub(" "; "") ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: column selector: query: 'true' port: entity: mappings: identifier: .__stateType + "/" +.name+ "/"+.__board.__project.id | gsub(" "; "") title: .name blueprint: '"azureDevopsColumn"' relations: board: .__board.id | gsub(" "; "") ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: release selector: query: 'true' port: entity: mappings: identifier: .projectReference.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsRelease"' properties: status: .status reason: .reason createdDate: .createdOn modifiedDate: .modifiedOn createdBy: .createdBy.uniqueName modifiedBy: .modifiedBy.uniqueName definitionName: .releaseDefinition.name link: ._links.web.href | gsub("_release?releaseId="; "") relations: {} ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `expand` | `string` | Property to expand on each release. When set, the expanded data is available in JQ mappings. | | `statusFilter` | `string` | Filter releases by status. | | `tagFilter` | `array` | List of tags to filter releases by. | | `sourceBranchFilter` | `string` | Filter releases by artifact source branch (e.g. 'refs/heads/main'). | | `minCreatedTime` | `string` | Only include releases created after this date (ISO 8601 format, e.g. '2025-01-01'). Ignored during incremental sync. | | `maxCreatedTime` | `string` | Only include releases created before this date (ISO 8601 format, e.g. '2026-01-01'). | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: environment selector: query: 'true' port: entity: mappings: identifier: .project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsEnvironment"' properties: description: .description createdOn: .createdOn lastModifiedOn: .lastModifiedOn relations: {} ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: release selector: query: 'true' port: entity: mappings: identifier: .projectReference.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsRelease"' properties: status: .status reason: .reason createdDate: .createdOn modifiedDate: .modifiedOn createdBy: .createdBy.uniqueName modifiedBy: .modifiedBy.uniqueName definitionName: .releaseDefinition.name link: ._links.web.href | gsub("_release?releaseId="; "") relations: {} - kind: release-deployment selector: query: '.completedOn != null and .completedOn != "0001-01-01T00:00:00"' port: entity: mappings: identifier: .releaseDefinition.projectReference.id + "/" + (.release.id | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .release.name + " - " + .releaseEnvironment.name blueprint: '"azureDevopsReleaseDeployment"' properties: status: .deploymentStatus url: .release.webAccessUri reason: .reason startedOn: >- .startedOn | if . == "0001-01-01T00:00:00" then null else . end completedOn: .completedOn requestedBy: .requestedBy.displayName operationStatus: .operationStatus environment: .releaseEnvironment.name relations: release: .releaseDefinition.projectReference.id + "/" + (.release.id | tostring) | gsub(" "; "") ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `updatedSince` | `string` | Only include release deployments modified after this date (ISO 8601). Ignored during incremental sync. | | `updatedUntil` | `string` | Only include release deployments modified before this date (ISO 8601). Ignored during incremental sync. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: environment selector: query: 'true' port: entity: mappings: identifier: .project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name | tostring blueprint: '"azureDevopsEnvironment"' properties: description: .description createdOn: .createdOn lastModifiedOn: .lastModifiedOn relations: {} - kind: pipeline-deployment selector: query: '.finishTime != null' port: entity: mappings: identifier: .__project.id + "/" + (.environmentId | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .owner.name // (.id | tostring) blueprint: '"azureDevopsPipelineDeployment"' properties: planType: .planType stageName: .stageName jobName: .jobName result: .result startTime: .startTime finishTime: .finishTime relations: environment: .__project.id + "/" + (.environmentId | tostring) | gsub(" "; "") build: .__project.id + "/" + (.owner.id | tostring) | gsub(" "; "") ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `defaultTeam` | `boolean` | If set to true, it ingests default team for each project to Port. This causes latency while syncing the entities to Port. Default value is false. Default: `false`. | Mapping exampleYAML **Integration mapping (click to expand)** Configuration Mapping for Azure DevOps File Ingestion The `selector` section within a `file` resource demonstrates how to configure file ingestion from Azure DevOps repositories. You can control: * **path:** Supports both explicit paths (e.g., `src/config.yaml`) and glob patterns (e.g., `**/*.yaml`) to ingest matching files. * **repos:** Target specific repositories or leave blank to scan all repositories. ``` resources: - kind: file selector: query: 'true' files: # You can use a single explicit path or glob pattern path: "src/config.yaml" # or "src/**/*.yaml" repos: ["my-repo", "another-repo"] # Optional: specific repositories to scan port: entity: mappings: identifier: .file.path | gsub(" "; "") title: .file.path blueprint: '"azureDevopsFile"' properties: path: .file.path size: .file.size content: .file.content.raw link: .repo.remoteUrl + "?path=" + (.file.path) isFolder: .file.isFolder extension: .file.extension relations: repository: .repo.id - kind: file selector: query: 'true' files: # You can use explicit paths or glob patterns in an array path: # Pass an array of explicit file paths - "deployment/helm/values.yaml" - "config/settings.json" - "docs/**/*.md" # Example: matches all .md files under docs/, including subdirectories - "**/package.json" # Example: match all package.json files across folders port: entity: mappings: identifier: .repo.name + "/" + .file.objectId title: .file.path blueprint: '"azureDevopsFile"' properties: path: .file.path size: .file.size content: .file.content.raw link: .repo.remoteUrl + "?path=" + (.file.path) isFolder: .file.isFolder extension: .file.extension relations: repository: .repo.id ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `files` | `object` | Configuration for file selection and scanning. | | `includedFiles` | `array` | List of file paths to fetch and attach to the file entity. This selector will add the content of the file to the API response under the `__includedFiles` field. | `files` parameters | Parameter | Type | Description | | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | `` | Explicit file path(s) to fetch, Can be a single path or a list of paths. For further details, [Check our docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/azure-devops/examples/#mapping-files). | | `repos` | `array` | List of repository names to scan. If None, scans all repositories. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: group selector: query: 'true' port: entity: mappings: identifier: .descriptor title: .displayName blueprint: '"azureDevopsGroup"' properties: description: .description url: .url - kind: group-member selector: query: 'true' port: entity: mappings: identifier: .__group.descriptor + "/" + .descriptor title: .displayName blueprint: '"azureDevopsGroupMember"' properties: email: .mailAddress subjectKind: .subjectKind relations: group: .__group.descriptor ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | ------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | Mapping exampleYAML **Integration mapping (click to expand)** ``` resources: - kind: group selector: query: 'true' port: entity: mappings: identifier: .descriptor title: .displayName blueprint: '"azureDevopsGroup"' properties: description: .description url: .url - kind: group-member selector: query: 'true' port: entity: mappings: identifier: .__group.descriptor + "/" + .descriptor title: .displayName blueprint: '"azureDevopsGroupMember"' properties: email: .mailAddress subjectKind: .subjectKind relations: group: .__group.descriptor ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | ------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | To connect work items to area paths (and optionally teams), see [Connect work items and teams to area paths](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/capabilities/.md#connect-work-items-and-teams-to-area-paths). ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Azure DevOps 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 Azure DevOps 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Azure DevOps is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Azure DevOps can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) Organization-level service hooks When `isProjectsLimited` is `false`, the integration creates organization-level Azure DevOps service hooks. Only the following hooks are subscribed: **Organization-level hooks (click to expand)** ``` organizationLevel: tfs: - git.pullrequest.created - git.pullrequest.updated - git.push - workitem.created - workitem.updated - workitem.commented - workitem.deleted - workitem.restored - build.complete rm: - ms.vss-release.release-created-event - ms.vss-release.release-abandoned-event - ms.vss-release.deployment-started-event - ms.vss-release.deployment-completed-event ``` Azure DevOps release service hooks use the `rm` publisher and are resolved through Azure DevOps's release host, `vsrm.dev.azure.com`. ## Incremental sync[​](#incremental-sync "Direct link to Incremental sync") In addition to the standard full resync, this integration supports [incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync/.md) for the following resource kinds: * `work-item` * `release` * `build` * `release-deployment` * `pipeline-run`
For incremental sync of `pipeline-run`, the integration discovers completed runs via the [Analytics OData API](https://learn.microsoft.com/en-us/azure/devops/report/analytics/analytics-query-parts) (`analytics.*`). Ensure the following: * **Azure DevOps Services**: Analytics is enabled by default. The credentials used by the integration need the **View analytics** permission on each project you sync. See [Analytics permissions and prerequisites](https://learn.microsoft.com/en-us/azure/devops/report/analytics/analytics-permissions-prerequisites?view=azure-devops). * **Azure DevOps Server**: Analytics must be [installed or enabled](https://learn.microsoft.com/en-us/azure/devops/report/dashboards/analytics-extension?view=azure-devops-2022) for each project collection. Full resync for pipeline runs uses the [Pipelines Runs REST API](https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/runs/list?view=azure-devops-rest-7.1\&tabs=HTTP#run) and does not require Analytics. * `advanced-security-alert` Any other kind in your mapping is not fetched incrementally, and continues to be kept up to date by the full resync. See [Incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync/.md) for how it works, its current limitations, and how to enable it for your organization. ## Capabilities[​](#capabilities "Direct link to Capabilities") Refer to the [capabilities](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/capabilities/.md) page for optional features such as file ingestion, area path configuration, and multi-entity file parsing. See also [Incremental sync](#incremental-sync) above for syncing recently changed resources on a short interval. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/examples/.md) page for practical configurations and their corresponding blueprint definitions. ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=AzureDevops). ## GitOps[​](#gitops "Direct link to GitOps") Port's Azure DevOps integration also provides GitOps capabilities, refer to the [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/gitops/.md) page to learn more. ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/advanced/.md) page for advanced use cases and examples. --- # Advanced The Azure Devops integration supports additional flags to provide additional configuration, making it easier to configure its behavior to your liking. To use the advanced configuration and additional flags, add them as a root key to your [integration configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#the-integration-configuration), for example to add the `createMissingRelatedEntities` flag: ``` createMissingRelatedEntities: false resources: - kind: pull-request selector: query: "true" port: entity: mappings: ... mappings configuration ``` ## Using advanced configurations[​](#using-advanced-configurations "Direct link to Using advanced configurations") The following advanced configuration parameters are available and can be added to the [integration configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#the-integration-configuration): * Spec path * Delete dependent entities * Enable merge entity * Create missing related entities * Branch * Use Default Branch Deprecated parameter The `specPath` parameter is deprecated. We recommend using the [file kind](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/#ingest-files-from-your-repositories) for GitOps instead, which provides more flexible file ingestion capabilities. The `specPath` parameter specifies a list of `string` or a single value `string` that Port's Azure Devops app will search for `port.yml` files in. * **Default value**: `port.yml` * **Use case**: * If you want the app to scan a different file than `port.yml` (for example, change configure the app to scan files named `my-port-config.yml` using the value `my-port-config.yml`). * If you want the app to ignore `port.yml` files in certain paths. The `deleteDependentEntities` parameter is used to enable deletion of dependent Port entities. This is useful when you have two blueprints with a required relation, and the target entity in the relation should be deleted. In this scenario, the delete operation will fail if this flag is set to `false` if the flag is set to `true`, the source entity will be deleted as well. * **Default value**: `false` (disabled) * **Use case**: Deletion of dependent Port entities. Must be enabled, if you want to delete a target entity (and its source entities) in a required relation. The `enableMergeEntity` parameter specifies whether to use the [create/update](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-update#usage) or [create/override](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-override#usage) strategy when creating entities listed in a `port.yml` file. * **Default value**: `false` (use create/override) * **Use case**: use `false` if you want Azure Devops to be the source-of-truth for catalog entities. Use `true` if you want to use Azure Devops as the source for some properties of entities in the catalog, and use other sources to for properties which are subject to change automatically. The `createMissingRelatedEntities` parameter is used to enable the creation of missing related Port entities automatically in cases where the target related entity does not exist in the software catalog yet. * **Default value**: `true` to allow the Azure Devops app to create barebones related entities, in case those related entities do not exist in the software catalog. * **Use case**: use `false` if you do not want this default behavior (do not create missing related entities). Time-based selectors with createMissingRelatedEntities set to false When you set `createMissingRelatedEntities: false` and use time-based selectors (filtering by `finishedDate`, `finishTime`, etc.) across parent and child kinds, entities with cross-kind relations may fail to create on first sync. Two scenarios can cause this: * **Selector gap**: a parent entity can be in progress while one of its children has already finished. For example, a `pipeline-run` (parent) can still be running while one of its `pipeline-stage` entities has already completed. A selector that requires `finishedDate != null` on the parent excludes the in-progress run, but a selector on the child that only requires `finishTime != null` includes the finished stage. The stage fails to create because the parent build entity does not exist in Port. This persists until the parent completes and is synced. * **Resync ordering**: even when both parent and child have finished, resync handlers run independently with no ordering guarantee. If the child kind is processed before the parent in the same resync cycle, the child entity fails to create. This is transient and self-resolves on the next resync. Setting `createMissingRelatedEntities: true` resolves both scenarios by creating a barebones entity for the relation target, which is replaced with the full entity when the parent is synced. Deprecated parameter The `branch` parameter is deprecated. We recommend using the [file kind](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/#ingest-files-from-your-repositories) for GitOps instead, which provides more flexible file ingestion capabilities. The Azure Devops integration listens to real-time events from the branch specified in the `branch` field which is set to `main` by default. This is configurable and you can set the `branch` parameter to the branch you want to listen to events from. * **Default value**: `main` (default to the branch specified in the `branch` field.) * **Use case**: `develop` if you want to listen to events from the `develop` branch of the repository. Use default branch If you want to listen to events from the default branch of the repository, you can set the `useDefaultBranch` parameter to `true`. The Azure Devops integration by default listens to events from the `main` branch. If you want to listen to events from the default branch of the repository, you can set the `useDefaultBranch` parameter to `true`. * **Default value**: `null` or `false` (default to the branch specified in the `branch` field.) * **Use case**: `true` if you want to listen to events from the default branch of the repository. --- # Capabilities ## Enrich entities with file contents[​](#enrich-entities-with-file-contents "Direct link to Enrich entities with file contents") You can enrich repository, folder, and file entities with the contents of specific files from your repositories. This is useful for including README files, CODEOWNERS, or other documentation directly in your entity properties. To do this, use the `includedFiles` selector to specify which files to fetch, and then reference them in your mapping using `.__includedFiles[""]`. ``` resources: - kind: repository selector: query: 'true' includedFiles: - README.md - CODEOWNERS port: entity: mappings: identifier: .id title: .name blueprint: '"azureDevopsRepository"' properties: readme: .__includedFiles["README.md"] codeowners: .__includedFiles["CODEOWNERS"] url: .remoteUrl ``` Deprecation notice The `file://` prefix in mappings is deprecated and will be removed in a future version. Use the `includedFiles` selector instead.
The `file://` prefix will continue to work but will show deprecation warnings in the logs. The `includedFiles` feature: * Fetches file contents from the repository's default branch (or specified branch for folder/file entities) * Stores file contents in the `__includedFiles` object in the raw data * Handles missing files gracefully (stores `null` if a file doesn't exist) * Works with repository, folder, and file entity types ## Ingest files from your repositories[​](#ingest-files-from-your-repositories "Direct link to Ingest files from your repositories") Port allows you to fetch `JSON` and `YAML` files from your repositories, and create entities from them in your software catalog.
This is done using the `file` kind in your Azure DevOps mapping configuration. For example, say you want to manage your `package.json` files in Port.
You will need to create a `manifest` blueprint, with each of its entities representing a `package.json` file. The following configuration fetches all `package.json` files from `my-project` and `my-other-project`, and creates an entity for each of them, based on the `manifest` blueprint: ``` resources: - kind: file selector: query: 'true' files: path: '**/package.json' repos: # Replace with your repository's path_with_namespace (e.g., "group/project" or "group/subgroup/project") - group/my-project - group/my-other-project ``` The `file` kind follows [Azure DevOps's Items Batch File Retrieval](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/items/get-items-batch?view=azure-devops-rest-7.1\&tabs=HTTP), adhering to its syntax, limitations, and capabilities. Test your mapping After adding the `file` kind to your mapping configuration, click on the `Resync` button.
When you open the mapping configuration again, you will see real examples of files fetched from your Azure DevOps organization. This will help you see what data is available to use in your `jq` expressions.
Click on the `Test mapping` button to test your mapping against the example data. The structure of the available data is as follows: **Available data example (click to expand)** ``` { "file": { "path": "integrations/services/configs/package.json", "objectId": "a8c19f9dba99f830e0b01d94b3bf17f4fa291b4f", "size": 9780, "isFolder": false, "commitId": "3ac12c7de037d7302a4be5204c9d4c5853ed6e20", "encoding": 1252, "contentType": "application/octet-stream", "fileName": "package.json", "extension": "json", "vsLink": "vsweb://vs/?Product=Visual_Studio&Gen=2013&EncFormat=UTF8&tfslink=dnN0ZnM6Ly8vR2l0L0xhdW5jaExhdGVzdFZlcnNpb25lZEl0ZW0vJTJmJTdiMDllOTQ5ZjktYWJhYy00OGFmLWE3MzYtNTc0OGJhZjc2OTlhJTdkJTJmJTJmaW50ZWdyYXRpb25zJTJmc2VydmljZXMlMmZjb25maWdzJTJmcGFja2FnZS5qc29uP3Byb2plY3Q9Nzc1NDk0OTItNjk4NC00Mzg5LWEyMDUtZGU0ZDc5NDE0MmFlJnVybD1odHRwcyUzQSUyRiUyRmRldi5henVyZS5jb20lMkZud2FvbWFjJTJG", "content": { "raw": "{\n \"name\": \"code-oss-dev\",\n \"version\": \"1.102.0\",\n \"distro\": \"969a2e84edcb47f53fbc4f8aa419dc7c062c71cf\",\n \"author\": {\n \"name\": \"Microsoft Corporation\"\n },\n \"license\": \"MIT\",\n \"main\": \"./out/main.js\",\n \"type\": \"module\",\n \"private\": true,\n \"scripts\": {\n \"test\": \"echo Please run any of the test scripts from the scripts folder.\",\n \"test-browser\": \"npx playwright install && node test/unit/browser/index.js\",\n \"test-browser-no-install\": \"node test/unit/browser/index.js\",\n \"test-node\": \"mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit\",\n \"test-extension\": \"vscode-test\",\n \"preinstall\": \"node build/npm/preinstall.js\",\n \"postinstall\": \"node build/npm/postinstall.js\",\n \"compile\": \"node ./node_modules/gulp/bin/gulp.js compile\",\n \"watch\": \"npm-run-all -lp watch-client watch-extensions\",\n \"watchd\": \"deemon npm run watch\",\n \"watch-webd\": \"deemon npm run watch-web\",\n \"kill-watchd\": \"deemon --kill npm run watch\",\n \"kill-watch-webd\": \"deemon --kill npm run watch-web\",\n \"restart-watchd\": \"deemon --restart npm run watch\",\n \"restart-watch-webd\": \"deemon --restart npm run watch-web\",\n \"watch-client\": \"node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js watch-client\",\n \"watch-clientd\": \"deemon npm run watch-client\",\n \"kill-watch-clientd\": \"deemon --kill npm run watch-client\",\n \"watch-extensions\": \"node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media\",\n \"watch-extensionsd\": \"deemon npm run watch-extensions\",\n \"kill-watch-extensionsd\": \"deemon --kill npm run watch-extensions\",\n \"precommit\": \"node build/hygiene.js\",\n \"gulp\": \"node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js\",\n \"electron\": \"node build/lib/electron\",\n \"7z\": \"7z\",\n \"update-grammars\": \"node build/npm/update-all-grammars.mjs\",\n \"update-localization-extension\": \"node build/npm/update-localization-extension.js\",\n \"smoketest\": \"node build/lib/preLaunch.js && cd test/smoke && npm run compile && node test/index.js\",\n \"smoketest-no-compile\": \"cd test/smoke && node test/index.js\",\n \"download-builtin-extensions\": \"node build/lib/builtInExtensions.js\",\n \"download-builtin-extensions-cg\": \"node build/lib/builtInExtensionsCG.js\",\n \"monaco-compile-check\": \"tsc -p src/tsconfig.monaco.json --noEmit\",\n \"tsec-compile-check\": \"node node_modules/tsec/bin/tsec -p src/tsconfig.tsec.json\",\n \"vscode-dts-compile-check\": \"tsc -p src/tsconfig.vscode-dts.json && tsc -p src/tsconfig.vscode-proposed-dts.json\",\n \"valid-layers-check\": \"node build/checker/layersChecker.js && tsc -p build/checker/tsconfig.browser.json && tsc -p build/checker/tsconfig.worker.json && tsc -p build/checker/tsconfig.node.json && tsc -p build/checker/tsconfig.electron-browser.json && tsc -p build/checker/tsconfig.electron-main.json && tsc -p build/checker/tsconfig.electron-utility.json\",\n \"define-class-fields-check\": \"node build/lib/propertyInitOrderChecker.js && tsc -p src/tsconfig.defineClassFields.json\",\n \"update-distro\": \"node build/npm/update-distro.mjs\",\n \"web\": \"echo 'npm run web' is replaced by './scripts/code-server' or './scripts/code-web'\",\n \"compile-cli\": \"gulp compile-cli\",\n \"compile-web\": \"node ./node_modules/gulp/bin/gulp.js compile-web\",\n \"watch-web\": \"node ./node_modules/gulp/bin/gulp.js watch-web\",\n \"watch-cli\": \"node ./node_modules/gulp/bin/gulp.js watch-cli\",\n \"eslint\": \"node build/eslint\",\n \"stylelint\": \"node build/stylelint\",\n \"playwright-install\": \"npm exec playwright install\",\n \"compile-build\": \"node ./node_modules/gulp/bin/gulp.js compile-build-with-mangling\",\n \"compile-extensions-build\": \"node ./node_modules/gulp/bin/gulp.js compile-extensions-build\",\n \"minify-vscode\": \"node ./node_modules/gulp/bin/gulp.js minify-vscode\",\n \"minify-vscode-reh\": \"node ./node_modules/gulp/bin/gulp.js minify-vscode-reh\",\n \"minify-vscode-reh-web\": \"node ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web\",\n \"hygiene\": \"node ./node_modules/gulp/bin/gulp.js hygiene\",\n \"core-ci\": \"node ./node_modules/gulp/bin/gulp.js core-ci\",\n \"core-ci-pr\": \"node ./node_modules/gulp/bin/gulp.js core-ci-pr\",\n \"extensions-ci\": \"node ./node_modules/gulp/bin/gulp.js extensions-ci\",\n \"extensions-ci-pr\": \"node ./node_modules/gulp/bin/gulp.js extensions-ci-pr\",\n \"perf\": \"node scripts/code-perf.js\",\n \"update-build-ts-version\": \"npm install typescript@next && tsc -p ./build/tsconfig.build.json\"\n },\n \"dependencies\": {\n \"@microsoft/1ds-core-js\": \"^3.2.13\",\n \"@microsoft/1ds-post-js\": \"^3.2.13\",\n \"@parcel/watcher\": \"2.5.1\",\n \"@types/semver\": \"^7.5.8\",\n \"@vscode/deviceid\": \"^0.1.1\",\n \"@vscode/iconv-lite-umd\": \"0.7.0\",\n \"@vscode/policy-watcher\": \"^1.3.2\",\n \"@vscode/proxy-agent\": \"^0.32.0\",\n \"@vscode/ripgrep\": \"^1.15.13\",\n \"@vscode/spdlog\": \"^0.15.2\",\n \"@vscode/sqlite3\": \"5.1.8-vscode\",\n \"@vscode/sudo-prompt\": \"9.3.1\",\n \"@vscode/tree-sitter-wasm\": \"^0.1.4\",\n \"@vscode/vscode-languagedetection\": \"1.0.21\",\n \"@vscode/windows-mutex\": \"^0.5.0\",\n \"@vscode/windows-process-tree\": \"^0.6.0\",\n \"@vscode/windows-registry\": \"^1.1.0\",\n \"@xterm/addon-clipboard\": \"^0.2.0-beta.95\",\n \"@xterm/addon-image\": \"^0.9.0-beta.112\",\n \"@xterm/addon-ligatures\": \"^0.10.0-beta.112\",\n \"@xterm/addon-progress\": \"^0.2.0-beta.18\",\n \"@xterm/addon-search\": \"^0.16.0-beta.112\",\n \"@xterm/addon-serialize\": \"^0.14.0-beta.112\",\n \"@xterm/addon-unicode11\": \"^0.9.0-beta.112\",\n \"@xterm/addon-webgl\": \"^0.19.0-beta.112\",\n \"@xterm/headless\": \"^5.6.0-beta.112\",\n \"@xterm/xterm\": \"^5.6.0-beta.112\",\n \"http-proxy-agent\": \"^7.0.0\",\n \"https-proxy-agent\": \"^7.0.2\",\n \"jschardet\": \"3.1.4\",\n \"kerberos\": \"2.1.1\",\n \"minimist\": \"^1.2.8\",\n \"native-is-elevated\": \"0.7.0\",\n \"native-keymap\": \"^3.3.5\",\n \"native-watchdog\": \"^1.4.1\",\n \"node-pty\": \"^1.1.0-beta33\",\n \"open\": \"^10.1.2\",\n \"tas-client-umd\": \"0.2.0\",\n \"v8-inspect-profiler\": \"^0.1.1\",\n \"vscode-oniguruma\": \"1.7.0\",\n \"vscode-regexpp\": \"^3.1.0\",\n \"vscode-textmate\": \"9.2.0\",\n \"yauzl\": \"^3.0.0\",\n \"yazl\": \"^2.4.3\"\n },\n \"devDependencies\": {\n \"@playwright/test\": \"^1.52.0\",\n \"@stylistic/eslint-plugin-ts\": \"^2.8.0\",\n \"@types/cookie\": \"^0.3.3\",\n \"@types/debug\": \"^4.1.5\",\n \"@types/eslint\": \"^9.6.1\",\n \"@types/gulp-svgmin\": \"^1.2.1\",\n \"@types/http-proxy-agent\": \"^2.0.1\",\n \"@types/kerberos\": \"^1.1.2\",\n \"@types/minimist\": \"^1.2.1\",\n \"@types/mocha\": \"^9.1.1\",\n \"@types/node\": \"22.x\",\n \"@types/sinon\": \"^10.0.2\",\n \"@types/sinon-test\": \"^2.4.2\",\n \"@types/trusted-types\": \"^1.0.6\",\n \"@types/vscode-notebook-renderer\": \"^1.72.0\",\n \"@types/webpack\": \"^5.28.5\",\n \"@types/wicg-file-system-access\": \"^2020.9.6\",\n \"@types/windows-foreground-love\": \"^0.3.0\",\n \"@types/winreg\": \"^1.2.30\",\n \"@types/yauzl\": \"^2.10.0\",\n \"@types/yazl\": \"^2.4.2\",\n \"@typescript-eslint/utils\": \"^8.8.0\",\n \"@vscode/gulp-electron\": \"^1.37.1\",\n \"@vscode/l10n-dev\": \"0.0.35\",\n \"@vscode/telemetry-extractor\": \"^1.10.2\",\n \"@vscode/test-cli\": \"^0.0.6\",\n \"@vscode/test-electron\": \"^2.4.0\",\n \"@vscode/test-web\": \"^0.0.62\",\n \"@vscode/v8-heap-parser\": \"^0.1.0\",\n \"@vscode/vscode-perf\": \"^0.0.19\",\n \"@webgpu/types\": \"^0.1.44\",\n \"ansi-colors\": \"^3.2.3\",\n \"asar\": \"^3.0.3\",\n \"chromium-pickle-js\": \"^0.2.0\",\n \"cookie\": \"^0.7.2\",\n \"copy-webpack-plugin\": \"^11.0.0\",\n \"css-loader\": \"^6.9.1\",\n \"debounce\": \"^1.0.0\",\n \"deemon\": \"^1.13.4\",\n \"electron\": \"35.6.0\",\n \"eslint\": \"^9.11.1\",\n \"eslint-formatter-compact\": \"^8.40.0\",\n \"eslint-plugin-header\": \"3.1.1\",\n \"eslint-plugin-jsdoc\": \"^50.3.1\",\n \"eslint-plugin-local\": \"^6.0.0\",\n \"event-stream\": \"3.3.4\",\n \"fancy-log\": \"^1.3.3\",\n \"file-loader\": \"^6.2.0\",\n \"glob\": \"^5.0.13\",\n \"gulp\": \"^4.0.0\",\n \"gulp-azure-storage\": \"^0.12.1\",\n \"gulp-bom\": \"^3.0.0\",\n \"gulp-buffer\": \"0.0.2\",\n \"gulp-filter\": \"^5.1.0\",\n \"gulp-flatmap\": \"^1.0.2\",\n \"gulp-gunzip\": \"^1.0.0\",\n \"gulp-gzip\": \"^1.4.2\",\n \"gulp-json-editor\": \"^2.5.0\",\n \"gulp-plumber\": \"^1.2.0\",\n \"gulp-rename\": \"^1.2.0\",\n \"gulp-replace\": \"^0.5.4\",\n \"gulp-sourcemaps\": \"^3.0.0\",\n \"gulp-svgmin\": \"^4.1.0\",\n \"gulp-untar\": \"^0.0.7\",\n \"husky\": \"^0.13.1\",\n \"innosetup\": \"^6.4.1\",\n \"istanbul-lib-coverage\": \"^3.2.0\",\n \"istanbul-lib-instrument\": \"^6.0.1\",\n \"istanbul-lib-report\": \"^3.0.0\",\n \"istanbul-lib-source-maps\": \"^4.0.1\",\n \"istanbul-reports\": \"^3.1.5\",\n \"lazy.js\": \"^0.4.2\",\n \"merge-options\": \"^1.0.1\",\n \"mime\": \"^1.4.1\",\n \"minimatch\": \"^3.0.4\",\n \"mocha\": \"^10.8.2\",\n \"mocha-junit-reporter\": \"^2.2.1\",\n \"mocha-multi-reporters\": \"^1.5.1\",\n \"npm-run-all\": \"^4.1.5\",\n \"os-browserify\": \"^0.3.0\",\n \"p-all\": \"^1.0.0\",\n \"path-browserify\": \"^1.0.1\",\n \"pump\": \"^1.0.1\",\n \"rcedit\": \"^1.1.0\",\n \"rimraf\": \"^2.2.8\",\n \"sinon\": \"^12.0.1\",\n \"sinon-test\": \"^3.1.3\",\n \"source-map\": \"0.6.1\",\n \"source-map-support\": \"^0.3.2\",\n \"style-loader\": \"^3.3.2\",\n \"ts-loader\": \"^9.5.1\",\n \"ts-node\": \"^10.9.1\",\n \"tsec\": \"0.2.7\",\n \"tslib\": \"^2.6.3\",\n \"typescript\": \"^5.9.0-dev.20250613\",\n \"typescript-eslint\": \"^8.8.0\",\n \"util\": \"^0.12.4\",\n \"webpack\": \"^5.94.0\",\n \"webpack-cli\": \"^5.1.4\",\n \"webpack-stream\": \"^7.0.0\",\n \"xml2js\": \"^0.5.0\",\n \"yaserver\": \"^0.4.0\"\n },\n \"overrides\": {\n \"node-gyp-build\": \"4.8.1\",\n \"kerberos@2.1.1\": {\n \"node-addon-api\": \"7.1.0\"\n }\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/microsoft/vscode.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/microsoft/vscode/issues\"\n },\n \"optionalDependencies\": {\n \"windows-foreground-love\": \"0.5.0\"\n }\n}", "parsed": { "name": "code-oss-dev", "version": "1.102.0", "distro": "969a2e84edcb47f53fbc4f8aa419dc7c062c71cf", "author": { "name": "Microsoft Corporation" }, "license": "MIT", "main": "./out/main.js", "type": "module", "private": true, "scripts": { "test": "echo Please run any of the test scripts from the scripts folder.", "test-browser": "npx playwright install && node test/unit/browser/index.js", "test-browser-no-install": "node test/unit/browser/index.js", "test-node": "mocha test/unit/node/index.js --delay --ui=tdd --timeout=5000 --exit", "test-extension": "vscode-test", "preinstall": "node build/npm/preinstall.js", "postinstall": "node build/npm/postinstall.js", "compile": "node ./node_modules/gulp/bin/gulp.js compile", "watch": "npm-run-all -lp watch-client watch-extensions", "watchd": "deemon npm run watch", "watch-webd": "deemon npm run watch-web", "kill-watchd": "deemon --kill npm run watch", "kill-watch-webd": "deemon --kill npm run watch-web", "restart-watchd": "deemon --restart npm run watch", "restart-watch-webd": "deemon --restart npm run watch-web", "watch-client": "node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js watch-client", "watch-clientd": "deemon npm run watch-client", "kill-watch-clientd": "deemon --kill npm run watch-client", "watch-extensions": "node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media", "watch-extensionsd": "deemon npm run watch-extensions", "kill-watch-extensionsd": "deemon --kill npm run watch-extensions", "precommit": "node build/hygiene.js", "gulp": "node --max-old-space-size=8192 ./node_modules/gulp/bin/gulp.js", "electron": "node build/lib/electron", "7z": "7z", "update-grammars": "node build/npm/update-all-grammars.mjs", "update-localization-extension": "node build/npm/update-localization-extension.js", "smoketest": "node build/lib/preLaunch.js && cd test/smoke && npm run compile && node test/index.js", "smoketest-no-compile": "cd test/smoke && node test/index.js", "download-builtin-extensions": "node build/lib/builtInExtensions.js", "download-builtin-extensions-cg": "node build/lib/builtInExtensionsCG.js", "monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit", "tsec-compile-check": "node node_modules/tsec/bin/tsec -p src/tsconfig.tsec.json", "vscode-dts-compile-check": "tsc -p src/tsconfig.vscode-dts.json && tsc -p src/tsconfig.vscode-proposed-dts.json", "valid-layers-check": "node build/checker/layersChecker.js && tsc -p build/checker/tsconfig.browser.json && tsc -p build/checker/tsconfig.worker.json && tsc -p build/checker/tsconfig.node.json && tsc -p build/checker/tsconfig.electron-browser.json && tsc -p build/checker/tsconfig.electron-main.json && tsc -p build/checker/tsconfig.electron-utility.json", "define-class-fields-check": "node build/lib/propertyInitOrderChecker.js && tsc -p src/tsconfig.defineClassFields.json", "update-distro": "node build/npm/update-distro.mjs", "web": "echo 'npm run web' is replaced by './scripts/code-server' or './scripts/code-web'", "compile-cli": "gulp compile-cli", "compile-web": "node ./node_modules/gulp/bin/gulp.js compile-web", "watch-web": "node ./node_modules/gulp/bin/gulp.js watch-web", "watch-cli": "node ./node_modules/gulp/bin/gulp.js watch-cli", "eslint": "node build/eslint", "stylelint": "node build/stylelint", "playwright-install": "npm exec playwright install", "compile-build": "node ./node_modules/gulp/bin/gulp.js compile-build-with-mangling", "compile-extensions-build": "node ./node_modules/gulp/bin/gulp.js compile-extensions-build", "minify-vscode": "node ./node_modules/gulp/bin/gulp.js minify-vscode", "minify-vscode-reh": "node ./node_modules/gulp/bin/gulp.js minify-vscode-reh", "minify-vscode-reh-web": "node ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web", "hygiene": "node ./node_modules/gulp/bin/gulp.js hygiene", "core-ci": "node ./node_modules/gulp/bin/gulp.js core-ci", "core-ci-pr": "node ./node_modules/gulp/bin/gulp.js core-ci-pr", "extensions-ci": "node ./node_modules/gulp/bin/gulp.js extensions-ci", "extensions-ci-pr": "node ./node_modules/gulp/bin/gulp.js extensions-ci-pr", "perf": "node scripts/code-perf.js", "update-build-ts-version": "npm install typescript@next && tsc -p ./build/tsconfig.build.json" }, "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "2.5.1", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.3.2", "@vscode/proxy-agent": "^0.32.0", "@vscode/ripgrep": "^1.15.13", "@vscode/spdlog": "^0.15.2", "@vscode/sqlite3": "5.1.8-vscode", "@vscode/sudo-prompt": "9.3.1", "@vscode/tree-sitter-wasm": "^0.1.4", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", "@xterm/addon-clipboard": "^0.2.0-beta.95", "@xterm/addon-image": "^0.9.0-beta.112", "@xterm/addon-ligatures": "^0.10.0-beta.112", "@xterm/addon-progress": "^0.2.0-beta.18", "@xterm/addon-search": "^0.16.0-beta.112", "@xterm/addon-serialize": "^0.14.0-beta.112", "@xterm/addon-unicode11": "^0.9.0-beta.112", "@xterm/addon-webgl": "^0.19.0-beta.112", "@xterm/headless": "^5.6.0-beta.112", "@xterm/xterm": "^5.6.0-beta.112", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", "jschardet": "3.1.4", "kerberos": "2.1.1", "minimist": "^1.2.8", "native-is-elevated": "0.7.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", "node-pty": "^1.1.0-beta33", "open": "^10.1.2", "tas-client-umd": "0.2.0", "v8-inspect-profiler": "^0.1.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "9.2.0", "yauzl": "^3.0.0", "yazl": "^2.4.3" }, "devDependencies": { "@playwright/test": "^1.52.0", "@stylistic/eslint-plugin-ts": "^2.8.0", "@types/cookie": "^0.3.3", "@types/debug": "^4.1.5", "@types/eslint": "^9.6.1", "@types/gulp-svgmin": "^1.2.1", "@types/http-proxy-agent": "^2.0.1", "@types/kerberos": "^1.1.2", "@types/minimist": "^1.2.1", "@types/mocha": "^9.1.1", "@types/node": "22.x", "@types/sinon": "^10.0.2", "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", "@types/vscode-notebook-renderer": "^1.72.0", "@types/webpack": "^5.28.5", "@types/wicg-file-system-access": "^2020.9.6", "@types/windows-foreground-love": "^0.3.0", "@types/winreg": "^1.2.30", "@types/yauzl": "^2.10.0", "@types/yazl": "^2.4.2", "@typescript-eslint/utils": "^8.8.0", "@vscode/gulp-electron": "^1.37.1", "@vscode/l10n-dev": "0.0.35", "@vscode/telemetry-extractor": "^1.10.2", "@vscode/test-cli": "^0.0.6", "@vscode/test-electron": "^2.4.0", "@vscode/test-web": "^0.0.62", "@vscode/v8-heap-parser": "^0.1.0", "@vscode/vscode-perf": "^0.0.19", "@webgpu/types": "^0.1.44", "ansi-colors": "^3.2.3", "asar": "^3.0.3", "chromium-pickle-js": "^0.2.0", "cookie": "^0.7.2", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.9.1", "debounce": "^1.0.0", "deemon": "^1.13.4", "electron": "35.6.0", "eslint": "^9.11.1", "eslint-formatter-compact": "^8.40.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^50.3.1", "eslint-plugin-local": "^6.0.0", "event-stream": "3.3.4", "fancy-log": "^1.3.3", "file-loader": "^6.2.0", "glob": "^5.0.13", "gulp": "^4.0.0", "gulp-azure-storage": "^0.12.1", "gulp-bom": "^3.0.0", "gulp-buffer": "0.0.2", "gulp-filter": "^5.1.0", "gulp-flatmap": "^1.0.2", "gulp-gunzip": "^1.0.0", "gulp-gzip": "^1.4.2", "gulp-json-editor": "^2.5.0", "gulp-plumber": "^1.2.0", "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.4", "gulp-sourcemaps": "^3.0.0", "gulp-svgmin": "^4.1.0", "gulp-untar": "^0.0.7", "husky": "^0.13.1", "innosetup": "^6.4.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-instrument": "^6.0.1", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.1", "istanbul-reports": "^3.1.5", "lazy.js": "^0.4.2", "merge-options": "^1.0.1", "mime": "^1.4.1", "minimatch": "^3.0.4", "mocha": "^10.8.2", "mocha-junit-reporter": "^2.2.1", "mocha-multi-reporters": "^1.5.1", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "p-all": "^1.0.0", "path-browserify": "^1.0.1", "pump": "^1.0.1", "rcedit": "^1.1.0", "rimraf": "^2.2.8", "sinon": "^12.0.1", "sinon-test": "^3.1.3", "source-map": "0.6.1", "source-map-support": "^0.3.2", "style-loader": "^3.3.2", "ts-loader": "^9.5.1", "ts-node": "^10.9.1", "tsec": "0.2.7", "tslib": "^2.6.3", "typescript": "^5.9.0-dev.20250613", "typescript-eslint": "^8.8.0", "util": "^0.12.4", "webpack": "^5.94.0", "webpack-cli": "^5.1.4", "webpack-stream": "^7.0.0", "xml2js": "^0.5.0", "yaserver": "^0.4.0" }, "overrides": { "node-gyp-build": "4.8.1", "kerberos@2.1.1": { "node-addon-api": "7.1.0" } }, "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" }, "bugs": { "url": "https://github.com/microsoft/vscode/issues" }, "optionalDependencies": { "windows-foreground-love": "0.5.0" } } } }, "repo": { "id": "09e949f9-abac-48af-a736-5748baf7699a", "name": "first-test", "url": "[REDACTED]/77549492-6984-4389-a205-de4d794142ae/_apis/git/repositories/09e949f9-abac-48af-a736-5748baf7699a", "project": { "id": "77549492-6984-4389-a205-de4d794142ae", "name": "first-test", "url": "[REDACTED]/_apis/projects/77549492-6984-4389-a205-de4d794142ae", "state": "wellFormed", "revision": 12, "visibility": "public", "lastUpdateTime": "2025-05-04T09:34:21.397Z" }, "defaultBranch": "refs/heads/master", "size": 1068, "remoteUrl": "https://username@dev.azure.com/username/first-test/_git/first-test", "sshUrl": "git@ssh.dev.azure.com:v3/username/first-test", "webUrl": "[REDACTED]/first-test/_git/first-test", "isDisabled": false, "isInMaintenance": false } } ``` ## Create multiple entities from a single file[​](#create-multiple-entities-from-a-single-file "Direct link to Create multiple entities from a single file") In some cases, we want to parse a single JSON or YAML file and create multiple entities from it.
To do this, we can use the `itemsToParse` key in our mapping configuration. For example, let's say we want to track or manage a project's dependencies in Port.
We’ll need to create a `package` blueprint, with each entity representing a dependency from a `package.json` file. The following configuration fetches a `package.json` file from a specific repository and creates an entity for each dependency in the file, based on the `package` blueprint: The following configuration fetches a `package.json` file from a specific repository and creates an entity for each dependency in the file, based on the `package` blueprint: ``` resources: - kind: file selector: query: 'true' files: path: '**/package.json' repos: - group/my-project port: itemsToParse: .file.content.parsed.dependencies | to_entries entity: mappings: # Since identifier cannot contain special characters, we are using jq to remove them identifier: >- .item.key + "_" + if (.item.value | startswith("^")) then .item.value[1:] else .item.value end title: .item.key + "@" + .item.value blueprint: '"package"' properties: package: .item.key version: .item.value relations: {} ``` The `itemsToParse` key is used to specify the path to the array of items you want to parse from the file. In this case, we are parsing the `dependencies` object from the `package.json` file. File content structure When Port ingests JSON or YAML files, the content is available in two forms: * `.file.content.raw` - The original file content as a string. * `.file.content.parsed` - The parsed JSON/YAML structure. Always use `.file.content.parsed` when accessing structured data from JSON or YAML files. Once the object is parsed, we can use the `item` key to refer to each key-value pair within it — where the key is the dependency name, and the value is the version. This allows us to create an entity for each dependency dynamically. ### Parsing a JSON array file[​](#parsing-a-json-array-file "Direct link to Parsing a JSON array file") If your file contains a JSON array (rather than an object), you can iterate over it directly. For example, if you have a `services.json` file: ```` [ { "name": "api-gateway", "port": 8080, "team": "platform" }, { "name": "auth-service", "port": 3000, "team": "security" } ] You can create an entity for each service using: ```yaml showLineNumbers resources: - kind: file selector: query: 'true' files: path: '**/services.json' repos: - group/my-project port: itemsToParse: .file.content.parsed[] entity: mappings: identifier: .item.name title: .item.name blueprint: '"service"' properties: port: .item.port team: .item.team relations: {} ```` Since `.file.content.parsed` is already an array, we use `.file.content.parsed[]` to iterate over each element. ### Multi-document YAML files[​](#multi-document-yaml-files "Direct link to Multi-document YAML files") For multi-document YAML files (a single file containing multiple YAML documents separated by `---`), `.file.content.parsed` will not resolve to an object, but to an array of objects. You can use one of these methods to ingest multi-document YAML files: 1. Use the `itemsToParse` key to create multiple entities from such a file (see example above). 2. Map the result to an `array` property. **Limitation:** Port does not support nested `itemsToParse` operations. When you have a multi-document YAML file (documents separated by `---`), the parsing engine automatically treats each document as a separate item in an array. This automatic splitting acts as an implicit `itemsToParse`, which means you cannot add an explicit `itemsToParse` to iterate over the documents. As a result, you cannot iterate over each YAML document in the multi-document file to create separate entities. Your only option is to ingest the entire multi-document file's content as a whole into a `string`, `markdown`, `json`, or `array` property. Mixed YAML types If you have both single-document and multi-document YAML files in your repositories, you can use the `itemsToParse` key like this to handle both cases: ``` itemsToParse: .file.content.parsed | if type== "object" then [.] else . end ``` ## Connect work items and teams to area paths[​](#connect-work-items-and-teams-to-area-paths "Direct link to Connect work items and teams to area paths") You can model your Azure DevOps [area paths](https://learn.microsoft.com/en-us/azure/devops/organizations/settings/about-areas-iterations) as catalog entities, connect each work item to the area path it belongs to, and surface the area paths assigned to each team. This helps you build views, scorecards, and automations organized by your project area structure. Use the `area-path` kind to ingest area path entities. The `team` kind is not included in the default mapping, but is available as an optional kind. If you add the `team` kind to your mapping configuration, you can opt in to the `includeAreaPaths` selector to enrich team entities with their assigned area paths. How area paths are ingested The integration fetches each project's area tree from the [Classification Nodes API](https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/classification-nodes/get-classification-nodes) and creates one `azureDevopsAreaPath` entity per node. Each node is identified by its GUID, and the tree hierarchy is preserved through a self-referential `parent` relation. ### Ingest area paths[​](#ingest-area-paths "Direct link to Ingest area paths") Use the optional `depth` selector to control how many levels of the area tree are fetched in a single call. It is not required - when omitted, the integration uses the Azure DevOps API's default depth. ``` - kind: area-path selector: query: 'true' depth: 10 # optional - number of area tree levels to fetch in a single call ``` ### Relate work items to area paths[​](#relate-work-items-to-area-paths "Direct link to Relate work items to area paths") The `azureDevopsWorkItem` blueprint includes an optional `areaPath` relation that connects each work item to the area path it belongs to. The relation is resolved with a [search relation](/context-lake/ingestion/configure-mapping/overview/.md#mapping-relations) rather than a direct identifier, because a work item's `System.AreaPath` value is not the same string as an area path node's `path`. A work item exposes its area as `System.AreaPath` (for example `Test Project\Backend`), while the Classification Nodes API returns the node `path` with a leading backslash and an `Area` structure-group segment (for example `\Test Project\Area\Backend`). To bridge the two, the integration computes a `normalizedPath` property on each area path entity by stripping the leading backslash and the `Area` segment. The result (`Test Project\Backend`) matches the work item's `System.AreaPath` value exactly: ``` relations: areaPath: combinator: '"and"' rules: - property: '"normalizedPath"' operator: '"="' value: .fields."System.AreaPath" ``` Area paths update on resync only Azure DevOps does not emit live webhook events for classification node changes. Area path entities are refreshed only on a full resync of the integration, not in real time. For complete blueprint definitions and integration configuration, refer to the [area paths examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/examples/.md#area-paths) page. ## Example Use Cases[​](#example-use-cases "Direct link to Example Use Cases") Common scenarios for file mapping include: * Tracking configuration files (e.g., `deployment.yaml`, `config.json`) * Monitoring documentation files (e.g., `README.md`, `CONTRIBUTING.md`) * Tracking infrastructure definitions (e.g., `terraform.tf`, `docker-compose.yml`) ## Best Practices[​](#best-practices "Direct link to Best Practices") * **Explicit paths and glob patterns supported**: You can use both explicit file paths relative to the repository root and wildcard/glob patterns (e.g., `*.yaml` or `**/*.json`) to specify which files to ingest. * **File Types**: Any plain-text or structured file (e.g., `.yaml`, `.json`, `.md`, `.py`) can be ingested. * **Path Structure**: Only relative paths from the repository root are currently supported. For example: * ✅ Correct paths: * `README.md` * `docs/getting-started.md` * `src/config/default.json` * `deployment/k8s/production.yaml` * `*.json` (glob pattern) * `**/*.yaml` (glob pattern) * `src/*.json` (glob pattern) * ❌ Incorrect paths: * `/README.md` (leading slash) * `C:/repo/config.json` (absolute path) * `../other-repo/file.txt` (parent directory reference) * **Performance**: For optimal performance, we recommend limiting the number of tracked files per repository. * **File Tracking**: Each file specified in the configuration will be tracked as a separate entity in Port * **Change Detection**: Changes to tracked files will be reflected in Port during the next sync ## Limitations[​](#limitations "Direct link to Limitations") * Only JSON and YAML formats are automatically parsed.
Other file formats can be ingested as raw files, however, some special characters in the file (such as `\n`) may be processed and not preserved. Learn more Click [here](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/items/get?view=azure-devops-rest-7.1\&tabs=HTTP#download) for the Azure DevOps file object structure. --- # Examples This page contains mapping examples, showing how to ingest various Azure Devops resources into Port. ## Projects[​](#projects "Direct link to Projects") The following example demonstrates how to ingest your Azure Devops projects and their default team (optional) to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'true' port: entity: mappings: identifier: .id | gsub(" "; "") title: .name blueprint: '"azureDevopsProject"' properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") ``` To Learn more * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/core/projects/list?view=azure-devops-rest-7.2\&tabs=HTTP#teamprojectreference) for the Azure Devops project object structure. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/core/projects/get?view=azure-devops-rest-7.2#teamproject) for the Azure Devops project object structure when `defaultTeam` is set to true. ## Repositories, repository policies and pull requests[​](#repositories-repository-policies-and-pull-requests "Direct link to Repositories, repository policies and pull requests") The following example demonstrates how to ingest your Azure Devops repositories, their README.md file contents and pull requests to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsRepository", "title": "Service", "icon": "AzureDevops", "schema": { "properties": { "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link" }, "readme": { "title": "README", "type": "string", "format": "markdown", "icon": "Book" }, "workItemLinking": { "title": "Work Item Linking", "default": false, "type": "boolean" }, "minimumApproverCount": { "title": "Minimum Approver Count", "default": 0, "type": "number" }, "slack": { "icon": "Slack", "type": "string", "title": "Slack", "format": "url" }, "tier": { "title": "Tier", "type": "string", "description": "How mission-critical the service is", "enum": [ "Mission Critical", "Customer Facing", "Internal Service", "Other" ], "enumColors": { "Mission Critical": "turquoise", "Customer Facing": "green", "Internal Service": "darkGray", "Other": "yellow" }, "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": { "defaultTeam": { "title": "Default Team", "path": "project.defaultTeam" } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "azureDevopsProject", "required": false, "many": false } } } ``` **Pull request blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsPullRequest", "title": "Pull Request", "icon": "AzureDevops", "schema": { "properties": { "status": { "title": "Status", "type": "string", "enum": [ "completed", "abandoned", "active" ], "enumColors": { "completed": "yellow", "abandoned": "red", "active": "green" } }, "createdAt": { "title": "Create At", "type": "string", "format": "date-time" }, "link": { "title": "Link", "format": "url", "type": "string" }, "leadTimeHours": { "title": "Lead Time in hours", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "repository", "target": "azureDevopsRepository", "required": false, "many": false }, "creator": { "title": "Creator", "target": "azureDevopsUser", "required": false, "many": false }, "reviewers": { "title": "Reviewers", "target": "azureDevopsUser", "required": false, "many": true } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'true' port: entity: mappings: identifier: '.id | gsub(" "; "")' blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .id title: .name blueprint: '"azureDevopsRepository"' properties: url: .remoteUrl readme: .__includedFiles["README.md"] relations: project: .project.id | gsub(" "; "") - kind: repository-policy selector: query: .type.displayName=="Minimum number of reviewers" port: entity: mappings: identifier: >- "\(.__repository.project.name | ascii_downcase | gsub("[ ();]"; ""))/\(.__repository.name | ascii_downcase | gsub("[ ();]"; ""))" blueprint: '"azureDevopsRepository"' properties: minimumApproverCount: .settings.minimumApproverCount - kind: repository-policy selector: query: .type.displayName=="Work item linking" port: entity: mappings: identifier: >- "\(.__repository.project.name | ascii_downcase | gsub("[ ();]"; ""))/\(.__repository.name | ascii_downcase | gsub("[ ();]"; ""))" blueprint: '"azureDevopsRepository"' properties: workItemLinking: .isEnabled and .isBlocking - kind: pull-request selector: query: 'true' port: entity: mappings: identifier: .repository.id + "/" + (.pullRequestId | tostring) blueprint: '"azureDevopsPullRequest"' properties: status: .status createdAt: .creationDate leadTimeHours: >- (.creationDate as $createdAt | .status as $status | .closedDate as $closedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($closedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $closedTimestamp | if $status == "completed" and $closedTimestamp != null then (((($closedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) else null end) relations: repository: .repository.id creator: .createdBy.uniqueName reviewers: '[.reviewers[].uniqueName]' ``` * Include minimum days * Include maximum results The `minTimeInDays` selector controls how many days back abandoned or completed pull requests are ingested per repository. The default is **7 days**. ``` - kind: pullRequest selector: query: 'true' minTimeInDays: 7 # default is 7 days ``` The `maxResults` selector controls how many abandoned or completed pull requests are ingested per repository. Its default value is **100 pull requests**. ``` - kind: pullRequest selector: query: 'true' maxResults: 100 # default is 100 ``` Performance impact Setting high values for `maxResults` or `minTimeInDays` on the `pull-request` kind may significantly slow down your integration. These configs make additional API calls for each pull request, which can be very resource-intensive. Consider setting these values to a lower number to improve performance. Further reading: * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Azure Devops objects to Port entities. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/repositories/list?view=azure-devops-rest-7.2\&tabs=HTTP#gitrepository) for the Azure Devops repository object structure. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/policy-configurations/get?view=azure-devops-rest-7.1#policyconfiguration) for the Azure Devops repository-policy object structure. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-requests/get-pull-requests?view=azure-devops-rest-7.1\&tabs=HTTP#gitpullrequest) for the Azure Devops pull-request object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your repositories alongside their `README.md` file contents and pull requests. ### Enrich pull requests with review metrics[​](#enrich-pull-requests-with-review-metrics "Direct link to Enrich pull requests with review metrics") The pull request list does not include commit history or review threads. Enable `enrichWithCommits` and `enrichWithReviewDiscussion` on the `pull-request` selector to attach that data and calculate coding time, review time, and time from approval to merge. Both flags default to `false`. Each enabled flag adds Azure DevOps API requests on resync and on live events. Completed pull requests still follow `minTimeInDays` (default 7 days) and `maxResults` (default 100 per repository). ``` - kind: pull-request selector: query: 'true' enrichWithCommits: true enrichWithReviewDiscussion: true ``` * `enrichWithCommits` attaches the commit list under `__commits`. The earliest commit time is `author.date`. * `enrichWithReviewDiscussion` attaches discussion threads under `__threads`. Lead time (`leadTimeHours`) is creation to completion and does not need these flags. The other timings do: | Metric | Property | Definition | | ----------------- | ------------------------- | ------------------------------------------------------------------------- | | Coding time | `codingTimeHours` | Earliest commit to ready for review. | | Review time | `timeToFirstReview` | Creation to the first review comment from someone other than the author. | | Approval to merge | `timeFromApprovalToMerge` | Latest standing approval by someone other than the author, to completion. | Review time skips threads whose type is `ReviewersUpdate`. `approvedAt` keeps each reviewer's latest `VoteUpdate` and uses the latest vote of 5 or higher (approved with suggestions, or approved). A later lower vote from the same reviewer replaces their earlier approval. Approvals by the pull request author are ignored. Time from approval to merge is set only when the pull request status is `completed`. **Edit the pull request blueprint** The default `azureDevopsPullRequest` blueprint does not include these properties. Edit the blueprint and add them before you use the mapping below: **Properties to add (click to expand)** ``` { "closedAt": { "title": "Closed At", "type": "string", "format": "date-time" }, "description": { "title": "Description", "type": "string" }, "authorId": { "title": "Author ID", "type": "string" }, "prNumber": { "title": "Pull Request Number", "type": "number" }, "isDraft": { "title": "Is Draft", "type": "boolean" }, "threads": { "title": "Threads", "type": "array", "items": { "type": "object" } }, "readyForReviewAt": { "title": "Ready For Review At", "type": "string", "format": "date-time", "description": "Falls back to createdAt when the pull request is not a draft. Uses the current draft state at sync time." }, "firstCommitAt": { "title": "First Commit At", "type": "string", "format": "date-time" }, "codingTimeHours": { "title": "Coding Time (Hours)", "type": "number" }, "firstReviewAt": { "title": "First Review At", "type": "string", "format": "date-time" }, "timeToFirstReview": { "title": "Time To First Review (Hours)", "type": "number" }, "approvedAt": { "title": "Approved At", "type": "string", "format": "date-time", "description": "Latest standing approval from someone other than the pull request author. A vote of 5 or higher counts." }, "timeFromApprovalToMerge": { "title": "Time From Approval To Merge (Hours)", "type": "number" } } ``` **Coding and review time mapping (click to expand)** ``` - kind: pull-request selector: query: 'true' enrichWithCommits: true enrichWithReviewDiscussion: true port: entity: mappings: identifier: .repository.id + "/" + (.pullRequestId | tostring) | gsub(" "; "") blueprint: '"azureDevopsPullRequest"' title: .title properties: status: .status createdAt: .creationDate closedAt: .closedDate description: .description authorId: .createdBy.id prNumber: .pullRequestId isDraft: .isDraft threads: .__threads leadTimeHours: >- (.creationDate as $createdAt | .status as $status | .closedDate as $closedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($closedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $closedTimestamp | if $status == "completed" and $closedTimestamp != null then (((($closedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) else null end) link: (.url | split("/_apis/")[0]) + "/_git/" + .repository.name + "/pullrequest/" + (.pullRequestId | tostring) readyForReviewAt: (if .isDraft == true then null else .creationDate end) firstCommitAt: ([.__commits[]? | select(.author.date != null)] | sort_by(.author.date) | first.author.date) // null codingTimeHours: >- ([.__commits[]? | select(.author.date != null)] | sort_by(.author.date) | first.author.date) as $first | (if .isDraft == true then null else .creationDate end) as $ready | if $first == null or $ready == null then null else ((($ready | sub("\\+00:00$"; "Z") | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($first | sub("\\+00:00$"; "Z") | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime)) / 3600 * 100 | floor) / 100 end) firstReviewAt: |- (.createdBy.id as $authorId | [.__threads[]? | . as $thread | ($thread.properties.CodeReviewThreadType["$value"] // null) as $threadType | $thread.comments[]? | select(.author.id != $authorId) | select($threadType != "ReviewersUpdate") | .publishedDate ] | sort | first) // null timeToFirstReview: >- (.creationDate) as $created | (.createdBy.id as $authorId | [.__threads[]? | . as $thread | ($thread.properties.CodeReviewThreadType["$value"] // null) as $threadType | $thread.comments[]? | select(.author.id != $authorId) | select($threadType != "ReviewersUpdate") | .publishedDate ] | sort | first) as $review | if $created == null or $review == null then null else ((($review | sub("\\+00:00$"; "Z") | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($created | sub("\\+00:00$"; "Z") | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime)) / 3600 * 100 | floor) / 100 end) approvedAt: >- (.createdBy.id as $authorId | ([.__threads[]? | select((.properties.CodeReviewThreadType["$value"] // null) == "VoteUpdate") | select(.comments[0].author.id != $authorId) | { authorId: .comments[0].author.id, publishedDate: .comments[0].publishedDate, vote: (.properties.CodeReviewVoteResult["$value"] | tonumber) } ] | group_by(.authorId) | map(sort_by(.publishedDate) | last) | map(select(.vote >= 5)) | sort_by(.publishedDate) | last.publishedDate)) // null timeFromApprovalToMerge: >- (.createdBy.id as $authorId | ([.__threads[]? | select((.properties.CodeReviewThreadType["$value"] // null) == "VoteUpdate") | select(.comments[0].author.id != $authorId) | { authorId: .comments[0].author.id, publishedDate: .comments[0].publishedDate, vote: (.properties.CodeReviewVoteResult["$value"] | tonumber) } ] | group_by(.authorId) | map(sort_by(.publishedDate) | last) | map(select(.vote >= 5)) | sort_by(.publishedDate) | last.publishedDate)) as $approved | (if .status == "completed" then .closedDate else null end) as $merged | if $approved == null or $merged == null then null else ((($merged | sub("\\+00:00$"; "Z") | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($approved | sub("\\+00:00$"; "Z") | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime)) / 3600 * 100 | floor) / 100 end) relations: repository: .repository.id ``` Timing limitations * `readyForReviewAt` uses the current draft state. A pull request opened as a draft and marked ready later is treated as ready at creation, which understates coding time and overstates review time. * Coding time is meaningful when your team opens draft pull requests. For a pull request opened directly, `readyForReviewAt` falls back to `createdAt`, so coding time is only the gap between the first commit and opening the pull request. * `approvedAt` excludes approvals by the pull request author. ## Pipelines[​](#pipelines "Direct link to Pipelines") The following example demonstrates how to ingest your Azure Devops pipelines to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Pipeline blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsPipeline", "title": "Pipeline", "icon": "AzureDevops", "schema": { "properties": { "url": { "type": "string", "format": "url", "title": "URL" }, "revision": { "type": "number", "title": "Revision" }, "folder": { "title": "Folder", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "azureDevopsRepository", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'true' port: entity: mappings: identifier: '.id | gsub(" "; "")' blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: pipeline selector: query: 'true' includeRepo: true port: entity: mappings: identifier: .__projectId + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsPipeline"' properties: url: ._links.web.href revision: .revision folder: .folder relations: repository: 'if .__repository then .__repository.id else null end' ``` To Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Azure Devops objects to Port entities. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/pipelines/list?view=azure-devops-rest-7.2#pipeline) for the Azure Devops pipeline object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port. ## Builds[​](#builds "Direct link to Builds") The following example demonstrates how to ingest your Azure DevOps builds to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Build blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsBuild", "title": "Build", "icon": "AzureDevops", "schema": { "properties": { "status": { "type": "string", "title": "Status" }, "result": { "type": "string", "title": "Result" }, "queueTime": { "type": "string", "format": "date-time", "title": "Queue Time" }, "startTime": { "type": "string", "format": "date-time", "title": "Start Time" }, "finishTime": { "type": "string", "format": "date-time", "title": "Finish Time" }, "definitionName": { "type": "string", "title": "Definition Name" }, "requestedFor": { "type": "string", "title": "Requested For" }, "sourceBranch": { "type": "string", "title": "Source Branch" }, "link": { "type": "string", "format": "url", "title": "Link" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "pipeline": { "title": "Pipeline", "target": "azureDevopsPipeline", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: build selector: query: '.finishTime != null' port: entity: mappings: identifier: .__project.id + "/" + (.id | tostring) | gsub(" "; "") title: .buildNumber blueprint: '"azureDevopsBuild"' properties: status: .status result: .result queueTime: .queueTime startTime: .startTime finishTime: .finishTime definitionName: .definition.name requestedFor: .requestedFor.displayName sourceBranch: .sourceBranch | gsub("refs/heads/"; "") link: ._links.web.href relations: pipeline: .__project.id + "/" + (.definition.id | tostring) ``` To Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Azure DevOps objects to Port entities. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/build/builds/list?view=azure-devops-rest-7.1\&tabs=HTTP#build) for the Azure DevOps build object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your builds. ### First commit enrichment for lead time[​](#first-commit-enrichment-for-lead-time "Direct link to First commit enrichment for lead time") When you set `enrichWithFirstCommit` to `true` on the `build` selector, the integration attaches a `__firstCommit` object to each build payload with the earliest commit shipped in that build (`__sha`, `__timestamp` in UTC, and `__commitCount`).
You can use these fields to compute [DORA Lead Time for Changes](/solutions/engineering-intelligence/measure-and-track-standards/dora-metrics/.md) at the build level, measured from the first commit to the build's finish time. If a build reports no timestamped changes, the integration falls back to the commit referenced by the build's `sourceVersion`. When no first-commit timestamp is available, the `leadTimeHours` property below resolves to `null`. To store the result, add a `leadTimeHours` number property to your build blueprint: **Lead time property (click to expand)** ``` { "leadTimeHours": { "type": "number", "title": "Lead Time (Hours)", "description": "Hours from the first commit shipped in the build to the build's finish time" } } ``` The following configuration enables the enrichment and maps `leadTimeHours`: ``` - kind: build selector: query: 'true' enrichWithFirstCommit: true port: entity: mappings: identifier: .__project.id + "/" + (.repository.id | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .buildNumber blueprint: '"azureDevopsBuild"' properties: status: .status result: .result finishTime: .finishTime leadTimeHours: >- if .__firstCommit.__timestamp and .finishTime then ((((.finishTime | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601) - (.__firstCommit.__timestamp | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601)) / 3600) * 100 | floor) / 100 else null end relations: project: .__project.id | gsub(" "; "") ``` Performance impact Enabling `enrichWithFirstCommit` on the `build` kind adds one extra API call per build to fetch its changes. This can slow down large resyncs. Leave it disabled unless you need build-level lead time. ## Pipeline stages[​](#pipeline-stages "Direct link to Pipeline stages") The following example demonstrates how to ingest your Azure DevOps pipeline stages to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Build blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsBuild", "title": "Build", "icon": "AzureDevops", "schema": { "properties": { "status": { "type": "string", "title": "Status" }, "result": { "type": "string", "title": "Result" }, "queueTime": { "type": "string", "format": "date-time", "title": "Queue Time" }, "startTime": { "type": "string", "format": "date-time", "title": "Start Time" }, "finishTime": { "type": "string", "format": "date-time", "title": "Finish Time" }, "definitionName": { "type": "string", "title": "Definition Name" }, "requestedFor": { "type": "string", "title": "Requested For" }, "sourceBranch": { "type": "string", "title": "Source Branch" }, "link": { "type": "string", "format": "url", "title": "Link" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "pipeline": { "title": "Pipeline", "target": "azureDevopsPipeline", "required": false, "many": false } } } ``` **Pipeline-stage blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsPipelineStage", "title": "Pipeline Stage", "icon": "AzureDevops", "schema": { "properties": { "state": { "type": "string", "title": "State" }, "result": { "type": "string", "title": "Result" }, "startTime": { "type": "string", "format": "date-time", "title": "Start Time" }, "finishTime": { "type": "string", "format": "date-time", "title": "Finish Time" }, "stageType": { "type": "string", "title": "Stage Type" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "build": { "title": "Build", "target": "azureDevopsBuild", "required": false, "many": false }, "pipelineRun": { "title": "Pipeline Run", "target": "azureDevopsPipelineRun", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: pipeline-stage selector: query: '.finishTime != null' port: entity: mappings: identifier: .__project.id + "/" + (.__build.id | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsPipelineStage"' properties: state: .state result: .result startTime: .startTime finishTime: .finishTime stageType: .type relations: build: .__project.id + "/" + (.__build.id | tostring) | gsub(" "; "") pipelineRun: .__project.id + "/" + (.__build.id | tostring) | gsub(" "; "") ``` To Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Azure DevOps objects to Port entities. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/runs/get?view=azure-devops-rest-7.1) for the Azure DevOps pipeline-stage object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your pipeline stages. ## Pipeline runs[​](#pipeline-runs "Direct link to Pipeline runs") The following example demonstrates how to ingest your Azure DevOps pipeline runs to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Pipeline-run blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsPipelineRun", "title": "Pipeline Run", "icon": "AzureDevops", "schema": { "properties": { "state": { "type": "string", "title": "State" }, "result": { "type": "string", "title": "Result" }, "createdDate": { "type": "string", "format": "date-time", "title": "Created Date" }, "finishedDate": { "type": "string", "format": "date-time", "title": "Finished Date" }, "pipelineName": { "type": "string", "title": "Pipeline Name" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "pipeline": { "title": "Pipeline", "target": "azureDevopsPipeline", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: pipeline-run selector: query: '.finishedDate != null' port: entity: mappings: identifier: .__project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsPipelineRun"' properties: state: .state result: .result createdDate: .createdDate finishedDate: .finishedDate pipelineName: .pipeline.name relations: pipeline: .__project.id + "/" + (.__pipeline.id | tostring) | gsub(" "; "") ``` To Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Azure DevOps objects to Port entities. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/runs/list?view=azure-devops-rest-7.1\&tabs=HTTP#run) for the Azure DevOps pipeline-run object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your pipeline runs. ## Branches[​](#branches "Direct link to Branches") The following example demonstrates how to ingest your Azure DevOps branches to Port.
You can use the following Port blueprint definitions and integration configuration: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsRepository", "title": "Service", "icon": "AzureDevops", "schema": { "properties": { "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link" }, "readme": { "title": "README", "type": "string", "format": "markdown", "icon": "Book" }, "workItemLinking": { "title": "Work Item Linking", "default": false, "type": "boolean" }, "minimumApproverCount": { "title": "Minimum Approver Count", "default": 0, "type": "number" }, "slack": { "icon": "Slack", "type": "string", "title": "Slack", "format": "url" }, "tier": { "title": "Tier", "type": "string", "description": "How mission-critical the service is", "enum": [ "Mission Critical", "Customer Facing", "Internal Service", "Other" ], "enumColors": { "Mission Critical": "turquoise", "Customer Facing": "green", "Internal Service": "darkGray", "Other": "yellow" }, "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": { "defaultTeam": { "title": "Default Team", "path": "project.defaultTeam" } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "azureDevopsProject", "required": false, "many": false } } } ``` **Branch blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsBranch", "title": "Branch", "icon": "AzureDevops", "schema": { "properties": { "repositoryName": { "title": "Repository Name", "type": "string", "icon": "AzureDevops", "description": "The name of the repository that contains this branch" }, "projectName": { "title": "Project Name", "type": "string", "icon": "AzureDevops", "description": "The name of the project that contains this branch" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "Link", "description": "Link to the branch in Azure DevOps" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "azureDevopsRepository", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .id title: .name blueprint: '"azureDevopsRepository"' properties: url: .remoteUrl readme: .__includedFiles["README.md"] id: .id last_activity: .project.lastUpdateTime relations: project: .project.id | gsub(" "; "") - kind: branch selector: query: 'true' port: entity: mappings: identifier: .objectId title: .name blueprint: '"azureDevopsBranch"' properties: repositoryName: .__repository.name projectName: .__repository.project.name link: .__repository.webUrl + "?version=GB" + .name relations: repository: .__repository.id ``` To Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Azure DevOps objects to Port entities. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/refs/list?view=azure-devops-rest-7.1#gitref) for the Azure DevOps refs object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your branches. ## Users[​](#users "Direct link to Users") Azure DevOps Server limitation The default `entitlements` source for the `user` kind is only available for Azure DevOps Services. It relies on the User Entitlements API, which is not available in Azure DevOps Server. To sync users from Azure DevOps Server, use the `graph` source (see [User selector](#user-selector) below), which reads from the Graph Users API. The following example blueprints and integration configurations demonstrate how to ingest Azure Devops users into Port: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **User blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsUser", "title": "Azure Devops User", "icon": "AzureDevops", "schema": { "properties": { "url": { "icon": "Link", "title": "URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: user selector: query: 'true' port: entity: mappings: identifier: .user.mailAddress title: .user.displayName blueprint: '"azureDevopsUser"' properties: url: .user.url - kind: project selector: query: 'true' defaultTeam: 'true' port: entity: mappings: identifier: '.id | gsub(" "; "")' blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") ``` ### User selector[​](#user-selector "Direct link to User selector") The `source` selector controls which Azure DevOps API the integration uses to fetch users: | Source | API | Required scope | Data returned | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ---------------------------------------------------- | | `entitlements` (default) | [User Entitlements API](https://learn.microsoft.com/en-us/rest/api/azure/devops/memberentitlementmanagement/user-entitlements/search-user-entitlements) | Elevated Member Entitlement Management permissions | User profile, entitlement, license, and billing data | | `graph` | [Graph Users API](https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/users/list?view=azure-devops-rest-7.1) | Read-only `vso.graph` | Basic user profile data | Choose `graph` when you only need basic user profile data with project entitlements, and want to avoid the elevated admin permissions required by the entitlements API. Keep the default `entitlements` source when you rely on entitlement or license data. Each source supports its own selectors, described in the tabs below. * Entitlements (default) * Graph The `entitlements` source uses the [User Entitlements API](https://learn.microsoft.com/en-us/rest/api/azure/devops/memberentitlementmanagement/user-entitlements/search-user-entitlements). It is the default, so you do not need to set `source` explicitly. It returns user profile data along with entitlement and license information. #### Include fields The `includeFields` selector allows you to include additional entitlement data on each user. By default, fields like `projectEntitlements` and `groupAssignments` are returned as empty arrays. The supported values for `includeFields` are: * `projects` - populates `projectEntitlements` (project access, team memberships, permission groups). * `groupRules` - populates `groupAssignments` (group entitlements the user belongs to). * `extensions` - populates extension-related entitlement data. * `license` - populates license assignment details. Permission requirement Using the `includeFields` selector requires the `ReadExtended Users` organization-level permission. This is not a PAT scope. If the permission is missing, the user sync will fail with an `Access Denied` error. To use the `includeFields` selector, the user who owns the PAT must be a member of the **Project Collection Administrators** group. Follow these steps to set it up: 1. In your [Azure DevOps organization settings](https://learn.microsoft.com/en-us/azure/devops/organizations/security/change-organization-collection-level-permissions?view=azure-devops), go to **Permissions** → **Project Collection Administrators** → **Members**. 2. Add the user whose PAT is used by the integration. 3. Ensure the PAT has the following scopes: `Member Entitlement Management (Read)`, `Extensions (Read)`, and `Extension Data (Read)`. There is no need to recreate the PAT - once the user has Project Collection Administrators membership, the existing token will work. To include project entitlements and group assignments, add the `includeFields` field to your user selector: ``` - kind: user selector: query: 'true' includeFields: - projects - groupRules ``` #### API version The `apiVersion` selector sets the `api-version` query parameter for the [User Entitlements API](https://learn.microsoft.com/en-us/rest/api/azure/devops/memberentitlementmanagement/user-entitlements/search-user-entitlements). When you omit this selector, the integration uses the default API version for your organization. Use `apiVersion` when your organization requires a specific API version, or when you need a response shape that matches an older version (for example `4.1-preview.1`). ``` - kind: user selector: query: 'true' apiVersion: '4.1-preview.1' ``` The integration adjusts pagination based on the API version you set. Versions before 7.x use `top`/`skip` pagination, while 7.x and later use continuation tokens. The `graph` source uses the [Graph Users API](https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/users/list?view=azure-devops-rest-7.1) with the read-only `vso.graph` scope, letting you sync users with a narrower permission footprint. To use it, set `source` to `graph`: ``` - kind: user selector: query: 'true' source: graph ``` Update your mapping The Graph source returns a different payload from the entitlements source. Its fields (`displayName`, `mailAddress`, `url`) are at the top level, not nested under `.user`, so you must update your user mapping accordingly. The `azureDevopsUser` blueprint remains unchanged. ``` - kind: user selector: query: 'true' source: graph port: entity: mappings: identifier: .mailAddress title: .displayName blueprint: '"azureDevopsUser"' properties: url: .url email: .mailAddress ``` The `graph` source supports two additional selectors: * `subjectTypes` - filters users by subject subtype, for example `aad`, `msa`, `svc`, or `imp`. * `includeGroupMemberships` - when set to `true`, enriches each user with their direct group memberships under `__groups` and the derived project entitlements under `__projects`, which you can reference in your mapping. ``` - kind: user selector: query: 'true' source: graph subjectTypes: - aad includeGroupMemberships: true ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#setup) section to learn more about the integration configuration setup process. * Port leverages the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Azure Devops objects to Port entities. * See the [Azure DevOps user entitlement object structure](https://learn.microsoft.com/en-us/rest/api/azure/devops/memberentitlementmanagement/user-entitlements/search-user-entitlements?view=azure-devops-rest-7.1) for available user properties. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your users. ## Work Items[​](#work-items "Direct link to Work Items") The following example demonstrates how to ingest your Azure Devops work items to Port.
You can use the following Port blueprint definitions and integration configuration: The default mapping includes a WIQL filter that excludes closed/done/removed work items and includes items changed in the last 7 days. You can modify or remove the `wiql` field in your mapping to adjust the filter. To ingest all work items, remove the `wiql` field entirely. Pagination Do not include an `ORDER BY` clause in your WIQL query. The integration injects one automatically for pagination, and adding one explicitly will disable pagination, capping returned work items to 20,000 per project. **Work item blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsWorkItem", "title": "Work Item", "icon": "AzureDevops", "schema": { "properties": { "type": { "title": "Type", "type": "string", "icon": "AzureDevops", "description": "The type of work item (e.g., Bug, Task, User Story)", "enum": [ "Issue", "Epic", "Task" ], "enumColors": { "Issue": "green", "Epic": "orange", "Task": "blue" } }, "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current state of the work item (e.g., New, Active, Closed)" }, "reason": { "title": "Reason", "type": "string", "description": "The title of the work item" }, "effort": { "title": "Effort", "type": "number", "description": "The estimated effort for the work item" }, "description": { "title": "Description", "type": "string", "format": "markdown", "description": "A detailed description of the work item" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to the work item in Azure DevOps" }, "createdBy": { "title": "Created By", "type": "string", "icon": "User", "description": "The person who created the work item" }, "changedBy": { "title": "Changed By", "type": "string", "icon": "User", "description": "The person who last changed the work item" }, "assignedTo": { "title": "Assigned To", "type": "string", "icon": "User", "description": "The person assigned to this work item" }, "createdDate": { "title": "Created Date", "type": "string", "format": "date-time", "description": "The date and time when the work item was created" }, "changedDate": { "title": "Changed Date", "type": "string", "format": "date-time", "description": "The date and time when the work item was last changed" } }, "required": [] }, "mirrorProperties": { "board": { "title": "Board", "path": "column.board.$title" } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "azureDevopsProject", "required": true, "many": false }, "column": { "title": "Column", "description": "The column the entity belongs", "target": "azureDevopsColumn", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: work-item selector: query: 'true' expand: 'All' wiql: "[System.State] NOT IN ('Closed','Done','Removed') OR [System.ChangedDate] >= @today - 7" port: entity: mappings: identifier: .id | tostring title: .fields."System.Title" blueprint: '"azureDevopsWorkItem"' properties: type: .fields."System.WorkItemType" state: .fields."System.State" effort: .fields."Microsoft.VSTS.Scheduling.Effort" description: .fields."System.Description" link: .url reason: .fields."System.Reason" createdBy: .fields."System.CreatedBy".displayName changedBy: .fields."System.ChangedBy".displayName assignedTo: .fields."System.AssignedTo".displayName createdDate: .fields."System.CreatedDate" changedDate: .fields."System.ChangedDate" relations: project: .__projectId | gsub(" "; "") column: >- .fields."System.WorkItemType" + "/" + .fields."System.BoardColumn" + "/" + .__project.id | gsub(" "; "") ``` To Learn more * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/work-items/list?view=azure-devops-rest-7.1\&tabs=HTTP#get-list-of-work-items) for the Azure Devops work item object structure. * Click [Here](https://learn.microsoft.com/en-us/azure/devops/boards/queries/wiql-syntax?view=azure-devops#example-clauses) for WIQL clauses ## Area paths[​](#area-paths "Direct link to Area paths") The following example demonstrates how to ingest your Azure DevOps area paths to Port.
Area paths are ingested per project and flattened into one entity per node, with a self-referential `parent` relation that preserves the area tree hierarchy. You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Area path blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsAreaPath", "title": "Area Path", "icon": "AzureDevops", "schema": { "properties": { "name": { "title": "Name", "type": "string", "icon": "AzureDevops", "description": "The name of the area path node" }, "path": { "title": "Path", "type": "string", "icon": "AzureDevops", "description": "The full hierarchical path of the area path as returned by Azure DevOps" }, "normalizedPath": { "title": "Normalized Path", "type": "string", "icon": "AzureDevops", "description": "The area path normalized to match the System.AreaPath value on work items" }, "hasChildren": { "title": "Has Children", "type": "boolean", "icon": "AzureDevops", "description": "Whether this area path has child nodes" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "azureDevopsProject", "required": false, "many": false }, "parent": { "title": "Parent", "description": "The parent area path node", "target": "azureDevopsAreaPath", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: area-path selector: query: 'true' port: entity: mappings: identifier: .identifier title: .name blueprint: '"azureDevopsAreaPath"' properties: name: .name path: .path normalizedPath: .__normalizedPath hasChildren: .hasChildren relations: project: .__projectId | gsub(" "; "") parent: .__parentIdentifier ``` Area paths update on resync only Azure DevOps does not emit live webhook events for classification node changes, so area path entities are refreshed only on a full resync of the integration. Further reading: * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Azure DevOps objects to Port entities. * Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/classification-nodes/get-classification-nodes) for the Azure DevOps classification nodes object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your area paths. ### Relating work items to area paths[​](#relating-work-items-to-area-paths "Direct link to Relating work items to area paths") Work items relate to area paths through an optional `areaPath` relation on the `azureDevopsWorkItem` blueprint. Add the following relation to your work item blueprint: ``` "areaPath": { "title": "Area Path", "description": "The area path the work item belongs to", "target": "azureDevopsAreaPath", "required": false, "many": false } ``` The relation is resolved with a [search relation](/context-lake/ingestion/configure-mapping/overview/.md#mapping-relations) on the area path's `normalizedPath` property, because a work item's `System.AreaPath` value (for example `Test Project\Backend`) is not the same string as an area path node's `path` (for example `\Test Project\Area\Backend`). The integration computes `normalizedPath` by stripping the leading backslash and the `Area` structure-group segment so the two values match: ``` - kind: work-item selector: query: 'true' port: entity: mappings: identifier: .id | tostring blueprint: '"azureDevopsWorkItem"' relations: areaPath: combinator: '"and"' rules: - property: '"normalizedPath"' operator: '"="' value: .fields."System.AreaPath" ``` Make sure the `azureDevopsAreaPath` blueprint above exists before ingesting work items so the relation can resolve. ## Boards[​](#boards "Direct link to Boards") The example below shows how to ingest Azure DevOps Boards into Port.
You can use the following Port blueprint definitions and integration configuration: **Board blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsBoard", "title": "Board", "icon": "AzureDevops", "schema": { "properties": { "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to the board in Azure DevOps" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "azureDevopsProject", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: board selector: query: 'true' port: entity: mappings: identifier: .id | gsub(" "; "") title: .name blueprint: '"azureDevopsBoard"' properties: link: .url relations: project: .__project.id | gsub(" "; "") ``` To Learn More * Click [here](https://learn.microsoft.com/en-us/rest/api/azure/devops/work/boards/list?view=azure-devops-rest-7.1) for the Azure DevOps board object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your Azure DevOps boards. ## Columns[​](#columns "Direct link to Columns") The example below shows how to ingest Azure DevOps Columns into Port.
You can use the following Port blueprint definitions and integration configuration: **Column blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsColumn", "title": "Column", "icon": "AzureDevops", "schema": { "properties": {}, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "board": { "title": "board", "target": "azureDevopsBoard", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: column selector: query: 'true' port: entity: mappings: identifier: .__stateType + "/" +.name+ "/"+.__board.__project.id | gsub(" "; "") title: .name blueprint: '"azureDevopsColumn"' relations: board: .__board.id | gsub(" "; "") ``` Learn More Click [here](https://learn.microsoft.com/en-us/rest/api/azure/devops/work/columns/list?view=azure-devops-rest-7.1) to learn about the Azure DevOps column object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your Azure DevOps columns. ## Releases[​](#releases "Direct link to Releases") The example below shows how to ingest Azure DevOps Releases into Port.
You can use the following Port blueprint definitions and integration configuration: **Release blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsRelease", "title": "Release", "icon": "AzureDevops", "schema": { "properties": { "status": { "title": "Status", "type": "string", "icon": "DefaultProperty", "description": "The current status of the release" }, "reason": { "title": "Reason", "type": "string", "description": "The reason for the release creation" }, "createdDate": { "title": "Created Date", "type": "string", "format": "date-time", "description": "The date and time when the release was created" }, "modifiedDate": { "title": "Modified Date", "type": "string", "format": "date-time", "description": "The date and time when the release was last modified" }, "createdBy": { "title": "Created By", "type": "string", "icon": "User", "description": "The person who created the release" }, "modifiedBy": { "title": "Modified By", "type": "string", "icon": "User", "description": "The person who last modified the release" }, "definitionName": { "title": "Definition Name", "type": "string", "description": "The name of the release definition" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to the release in Azure DevOps" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: release selector: query: 'true' port: entity: mappings: identifier: .projectReference.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsRelease"' properties: status: .status reason: .reason createdDate: .createdOn modifiedDate: .modifiedOn createdBy: .createdBy.uniqueName modifiedBy: .modifiedBy.uniqueName definitionName: .releaseDefinition.name link: ._links.web.href | gsub("_release?releaseId="; "") relations: {} ``` * Expand * Status filter * Tag filter * Source branch filter * Date filters The `expand` selector allows you to include additional data on each release. By default, no properties are expanded. The supported values for `expand` are: * `environments` * `artifacts` * `approvals` * `manualInterventions` * `variables` * `tags` ``` - kind: release selector: query: 'true' expand: environments ``` The `statusFilter` selector filters releases by their current status. By default, all statuses are included. The supported values for `statusFilter` are: * `abandoned` * `active` * `draft` * `undefined` ``` - kind: release selector: query: 'true' statusFilter: active ``` The `tagFilter` selector filters releases by tags. By default, no tag filter is applied. ``` - kind: release selector: query: 'true' tagFilter: - production - stable ``` The `sourceBranchFilter` selector filters releases by the artifact source branch. By default, no branch filter is applied. ``` - kind: release selector: query: 'true' sourceBranchFilter: refs/heads/main ``` The `minCreatedTime` and `maxCreatedTime` selectors filter releases by creation date. Provide dates in ISO 8601 format. By default, no date filter is applied. ``` - kind: release selector: query: 'true' minCreatedTime: '2025-01-01' maxCreatedTime: '2026-01-01' ``` Learn more Click [here](https://learn.microsoft.com/en-us/rest/api/azure/devops/release/releases/list?view=azure-devops-rest-7.1\&tabs=HTTP) for the Azure DevOps release object structure. ## Release definitions[​](#release-definitions "Direct link to Release definitions") The following example demonstrates how to ingest Azure DevOps release definitions to Port. Release definitions describe Classic Release pipeline templates, including their stages, artifacts, triggers, and variables. **Release definition blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsReleaseDefinition", "title": "Release Definition", "icon": "AzureDevops", "schema": { "properties": { "description": { "title": "Description", "type": "string", "description": "The description of the release definition" }, "createdDate": { "title": "Created Date", "type": "string", "format": "date-time", "description": "The date and time when the release definition was created" }, "modifiedDate": { "title": "Modified Date", "type": "string", "format": "date-time", "description": "The date and time when the release definition was last modified" }, "createdBy": { "title": "Created By", "type": "string", "icon": "User", "description": "The person who created the release definition" }, "modifiedBy": { "title": "Modified By", "type": "string", "icon": "User", "description": "The person who last modified the release definition" }, "releaseNameFormat": { "title": "Release Name Format", "type": "string", "description": "The format used to generate release names" }, "path": { "title": "Path", "type": "string", "description": "The folder path of the release definition" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to the release definition in Azure DevOps" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "azureDevopsProject", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: release-definition selector: query: 'true' port: entity: mappings: identifier: .__project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsReleaseDefinition"' properties: description: .description createdDate: .createdOn modifiedDate: .modifiedOn createdBy: .createdBy.uniqueName modifiedBy: .modifiedBy.uniqueName releaseNameFormat: .releaseNameFormat path: .path link: ._links.web.href relations: project: .__project.id | gsub(" "; "") ``` * Expand * Tag filter The `expand` selector controls which additional data is included on each release definition. The default is `environments`, which provides stage information. The supported values for `expand` are: * `environments` (default) * `artifacts` * `triggers` * `variables` * `tags` * `lastRelease` ``` - kind: release-definition selector: query: 'true' expand: environments ``` The `tagFilter` selector filters release definitions by tags. By default, no tag filter is applied and all definitions are returned. ``` - kind: release-definition selector: query: 'true' tagFilter: - production - stable ``` Learn more Click [here](https://learn.microsoft.com/en-us/rest/api/azure/devops/release/definitions/list?view=azure-devops-rest-7.1) for the Azure DevOps release definition object structure. ## Files[​](#files "Direct link to Files") The example below shows how to ingest specific files from your Azure DevOps repositories into Port. This integration allows you to track and monitor individual files across your repositories. ### Configuration[​](#configuration "Direct link to Configuration") You can use the following Port blueprint definitions and integration configuration: **File blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsFile", "title": "File", "icon": "AzureDevops", "schema": { "properties": { "path": { "title": "Path", "type": "string", "icon": "DefaultProperty", "description": "The path of the file in the repository" }, "size": { "title": "Size", "type": "number", "icon": "DefaultProperty", "description": "The size of the file in bytes" }, "content": { "title": "Content", "type": "string", "icon": "DefaultProperty", "description": "The content of the file" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "Link", "description": "Link to the file in Azure DevOps" }, "isFolder": { "type": "boolean", "title": "Is Folder" }, "extension": { "type": "string", "title": "Extension" } }, "required": ["path"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "azureDevopsRepository", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** Configuration Mapping for Azure DevOps File Ingestion The `selector` section within a `file` resource demonstrates how to configure file ingestion from Azure DevOps repositories. You can control: * **path:** Supports both explicit paths (e.g., `src/config.yaml`) and glob patterns (e.g., `**/*.yaml`) to ingest matching files. * **repos:** Target specific repositories or leave blank to scan all repositories. ``` resources: - kind: file selector: query: 'true' files: # You can use a single explicit path or glob pattern path: "src/config.yaml" # or "src/**/*.yaml" repos: ["my-repo", "another-repo"] # Optional: specific repositories to scan port: entity: mappings: identifier: .file.path | gsub(" "; "") title: .file.path blueprint: '"azureDevopsFile"' properties: path: .file.path size: .file.size content: .file.content.raw link: .repo.remoteUrl + "?path=" + (.file.path) isFolder: .file.isFolder extension: .file.extension relations: repository: .repo.id - kind: file selector: query: 'true' files: # You can use explicit paths or glob patterns in an array path: # Pass an array of explicit file paths - "deployment/helm/values.yaml" - "config/settings.json" - "docs/**/*.md" # Example: matches all .md files under docs/, including subdirectories - "**/package.json" # Example: match all package.json files across folders port: entity: mappings: identifier: .repo.name + "/" + .file.objectId title: .file.path blueprint: '"azureDevopsFile"' properties: path: .file.path size: .file.size content: .file.content.raw link: .repo.remoteUrl + "?path=" + (.file.path) isFolder: .file.isFolder extension: .file.extension relations: repository: .repo.id ``` Learn more Click [here](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/items/get?view=azure-devops-rest-7.1\&tabs=HTTP#download) for the Azure DevOps file object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your specified files. ## Repositories and monorepos[​](#repositories-and-monorepos "Direct link to Repositories and monorepos") The following example shows how to ingest your Azure Devops repositories and their folders into Port. By following this example you can map your different repositories, packages and libraries from your monorepo into separate entities in Port. **Note** that mapping is configured per project, so you need to create a separate mapping block for each project you want to ingest. **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsRepository", "title": "Service", "icon": "AzureDevops", "schema": { "properties": { "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link" }, "readme": { "title": "README", "type": "string", "format": "markdown", "icon": "Book" }, "workItemLinking": { "title": "Work Item Linking", "default": false, "type": "boolean" }, "minimumApproverCount": { "title": "Minimum Approver Count", "default": 0, "type": "number" }, "slack": { "icon": "Slack", "type": "string", "title": "Slack", "format": "url" }, "tier": { "title": "Tier", "type": "string", "description": "How mission-critical the service is", "enum": [ "Mission Critical", "Customer Facing", "Internal Service", "Other" ], "enumColors": { "Mission Critical": "turquoise", "Customer Facing": "green", "Internal Service": "darkGray", "Other": "yellow" }, "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": { "defaultTeam": { "title": "Default Team", "path": "project.defaultTeam" } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "azureDevopsProject", "required": false, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: folder selector: project_name: 'project_001' # Project name in Azure DevOps that contains the repositories query: 'true' # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: src/ # path to the folders within the repositories. repos: # List of repositories to include folders from. - name: sample_repo branch: main port: entity: mappings: identifier: .objectId title: .path url: .__repository.remoteUrl blueprint: '"azureDevopsFolder"' properties: defaultBranch: .__repository.defaultBranch relations: project: .__repository.project.id | gsub(" "; "") ``` ## Environments[​](#environments "Direct link to Environments") The following example demonstrates how to ingest Azure DevOps environments to Port. Environments represent deployment targets in Azure DevOps and are used to track deployments across different stages. **Environment blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsEnvironment", "title": "Azure DevOps Environment", "icon": "AzureDevops", "schema": { "properties": { "description": { "title": "Description", "type": "string", "icon": "DefaultProperty", "description": "The description of the environment" }, "createdOn": { "title": "Created On", "type": "string", "format": "date-time", "description": "The date and time when the environment was created" }, "lastModifiedOn": { "title": "Last Modified On", "type": "string", "format": "date-time", "description": "The date and time when the environment was last modified" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: environment selector: query: 'true' port: entity: mappings: identifier: .project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsEnvironment"' properties: description: .description createdOn: .createdOn lastModifiedOn: .lastModifiedOn relations: {} ``` ## Release deployments[​](#release-deployments "Direct link to Release deployments") The following example demonstrates how to ingest Azure DevOps release deployments to Port. Release deployments track the deployment of releases to specific environments, providing visibility into your Classic Release pipeline deployments. **Release Deployment blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsReleaseDeployment", "title": "Azure DevOps Release Deployment", "icon": "AzureDevops", "schema": { "properties": { "status": { "title": "Status", "type": "string", "icon": "DefaultProperty", "description": "The deployment status" }, "url": { "title": "URL", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to the deployment in Azure DevOps" }, "reason": { "title": "Reason", "type": "string", "description": "The reason for the deployment" }, "startedOn": { "title": "Started On", "type": "string", "format": "date-time", "description": "The date and time when the deployment started" }, "completedOn": { "title": "Completed On", "type": "string", "format": "date-time", "description": "The date and time when the deployment completed" }, "requestedBy": { "title": "Requested By", "type": "string", "icon": "User", "description": "The person who requested the deployment" }, "operationStatus": { "title": "Operation Status", "type": "string", "description": "The operation status of the deployment" }, "environment": { "title": "Environment", "type": "string", "description": "The target environment name" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "release": { "title": "Release", "target": "azureDevopsRelease", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: release selector: query: 'true' port: entity: mappings: identifier: .projectReference.id + "/" + (.id | tostring) | gsub(" "; "") title: .name blueprint: '"azureDevopsRelease"' properties: status: .status reason: .reason createdDate: .createdOn modifiedDate: .modifiedOn createdBy: .createdBy.uniqueName modifiedBy: .modifiedBy.uniqueName definitionName: .releaseDefinition.name link: ._links.web.href | gsub("_release?releaseId="; "") relations: {} - kind: release-deployment selector: query: '.completedOn != null and .completedOn != "0001-01-01T00:00:00"' port: entity: mappings: identifier: .releaseDefinition.projectReference.id + "/" + (.release.id | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .release.name + " - " + .releaseEnvironment.name blueprint: '"azureDevopsReleaseDeployment"' properties: status: .deploymentStatus url: .release.webAccessUri reason: .reason startedOn: >- .startedOn | if . == "0001-01-01T00:00:00" then null else . end completedOn: .completedOn requestedBy: .requestedBy.displayName operationStatus: .operationStatus environment: .releaseEnvironment.name relations: release: .releaseDefinition.projectReference.id + "/" + (.release.id | tostring) | gsub(" "; "") ``` ## Pipeline deployments[​](#pipeline-deployments "Direct link to Pipeline deployments") The following example demonstrates how to ingest Azure DevOps pipeline deployments to Port. Pipeline deployments track deployments from YAML pipelines to environments, providing visibility into your modern CI/CD pipeline deployments. **Pipeline Deployment blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsPipelineDeployment", "title": "Azure DevOps Pipeline Deployment", "icon": "AzureDevops", "schema": { "properties": { "planType": { "title": "Plan Type", "type": "string", "icon": "DefaultProperty", "description": "The type of deployment plan" }, "stageName": { "title": "Stage Name", "type": "string", "description": "The name of the deployment stage" }, "jobName": { "title": "Job Name", "type": "string", "description": "The name of the deployment job" }, "result": { "title": "Result", "type": "string", "description": "The result of the deployment" }, "startTime": { "title": "Start Time", "type": "string", "format": "date-time", "description": "The date and time when the deployment started" }, "finishTime": { "title": "Finish Time", "type": "string", "format": "date-time", "description": "The date and time when the deployment finished" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "environment": { "title": "Environment", "target": "azureDevopsEnvironment", "required": false, "many": false }, "build": { "title": "Build", "target": "azureDevopsBuild", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: 'true' defaultTeam: 'false' port: entity: mappings: identifier: .id | gsub(" "; "") blueprint: '"azureDevopsProject"' title: .name properties: state: .state revision: .revision visibility: .visibility defaultTeam: .defaultTeam.name link: .url | gsub("_apis/projects/"; "") - kind: environment selector: query: 'true' port: entity: mappings: identifier: .project.id + "/" + (.id | tostring) | gsub(" "; "") title: .name | tostring blueprint: '"azureDevopsEnvironment"' properties: description: .description createdOn: .createdOn lastModifiedOn: .lastModifiedOn relations: {} - kind: pipeline-deployment selector: query: '.finishTime != null' port: entity: mappings: identifier: .__project.id + "/" + (.environmentId | tostring) + "/" + (.id | tostring) | gsub(" "; "") title: .owner.name // (.id | tostring) blueprint: '"azureDevopsPipelineDeployment"' properties: planType: .planType stageName: .stageName jobName: .jobName result: .result startTime: .startTime finishTime: .finishTime relations: environment: .__project.id + "/" + (.environmentId | tostring) | gsub(" "; "") build: .__project.id + "/" + (.owner.id | tostring) | gsub(" "; "") ``` ## Advanced security alerts[​](#advanced-security-alerts "Direct link to Advanced security alerts") The following example demonstrates how to ingest your Azure Devops advanced security alerts to Port.
You can use the following Port blueprint definitions and integration configuration: **Advanced Security Alert blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsAdvancedSecurity", "title": "Azure DevOps Advanced Security", "icon": "AzureDevops", "schema": { "properties": { "severity": { "title": "Severity", "type": "string", "description": "The severity level of the security alert", "enum": [ "low", "medium", "high", "critical", "note", "warning", "error" ], "enumColors": { "low": "blue", "medium": "yellow", "high": "orange", "critical": "red", "note": "blue", "warning": "yellow", "error": "red" } }, "state": { "title": "State", "type": "string", "description": "The current state of the security alert", "enum": [ "active", "dismissed", "fixed", "autoDismissed" ], "enumColors": { "active": "red", "dismissed": "darkGray", "fixed": "green", "autoDismissed": "darkGray" } }, "alertType": { "title": "Alert Type", "type": "string", "description": "The type of security alert", "enum" : [ "dependency", "secret", "code" ], "enumColors": { "dependency": "blue", "secret": "yellow", "code": "orange" } }, "confidence": { "title": "Confidence", "type": "string", "description": "The confidence level of the security alert", "enum" : [ "other", "high" ], "enumColors": { "other": "yellow", "high": "green" } }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "The URL of the security alert in Azure DevOps" }, "introducedDate": { "title": "Introduced Date", "type": "string", "format": "date-time", "description": "The date when the security alert was introduced" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "azureDevopsRepository", "required": true, "many": false }, "project": { "title": "Project", "target": "azureDevopsProject", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: advanced-security-alert selector: query: 'true' port: entity: mappings: identifier: .__repositoryId + "/" + (.alertId | tostring) title: .title blueprint: '"azureDevopsAdvancedSecurity"' properties: severity: .severity state: .state alertType: .alertType introducedDate: .introducedDate confidence: .confidence link: .physicalLocations[0].versionControl.itemUrl relations: repository: .__repositoryId project: .__projectId ``` * States criteria * Severities criteria * Alert Type criteria The `states` filter under the `criteria` selector controls which states of the advanced security alerts are ingested per repository. By default, no states are provided, so all alerts are ingested. The supported values for `states` are: * `active` * `dismissed` * `fixed` * `autoDismissed` ``` - kind: advanced-security-alert selector: query: 'true' criteria: states: - active - dismissed ``` The `severities` filter under the `criteria` selector controls which severities of the advanced security alerts are ingested per repository. By default, no severities are provided, so all alerts are ingested. The supported values for `severities` are: * `low` * `medium` * `high` * `critical` * `note` * `warning` * `error` ``` - kind: advanced-security-alert selector: query: 'true' criteria: severities: - high - critical ``` The `alertType` filter under the `criteria` selector controls which alert types of the advanced security alerts are ingested per repository. By default, no alert types are provided, so all alerts are ingested. The supported values for `alertType` are: * `code` * `dependency` * `secret` ``` - kind: advanced-security-alert selector: query: 'true' criteria: alertType: 'secret' ``` To Learn more Click [Here](https://learn.microsoft.com/en-us/rest/api/azure/devops/advancedsecurity/alerts/list?view=azure-devops-rest-7.2\&tabs=HTTP) for the Azure Devops advanced security alert object structure. ## Groups and group members[​](#groups-and-group-members "Direct link to Groups and group members") The following example demonstrates how to ingest your Azure DevOps security groups and their direct members to Port. This is useful for mapping PR reviewers, understanding team structures, and managing access control. You can use the following Port blueprint definitions and integration configuration: **Group blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsGroup", "title": "Azure DevOps Group", "icon": "AzureDevops", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "url": { "type": "string", "format": "url", "title": "URL" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Group Member blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsGroupMember", "title": "Azure DevOps Group Member", "icon": "AzureDevops", "schema": { "properties": { "email": { "type": "string", "title": "Email" }, "subjectKind": { "type": "string", "title": "Subject Kind", "enum": ["user", "group"] } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "group": { "title": "Group", "target": "azureDevopsGroup", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: group selector: query: 'true' port: entity: mappings: identifier: .descriptor title: .displayName blueprint: '"azureDevopsGroup"' properties: description: .description url: .url - kind: group-member selector: query: 'true' port: entity: mappings: identifier: .__group.descriptor + "/" + .descriptor title: .displayName blueprint: '"azureDevopsGroupMember"' properties: email: .mailAddress subjectKind: .subjectKind relations: group: .__group.descriptor ``` Direct members only The integration fetches only direct members of each group. Nested group memberships are not recursively expanded. If a group contains another group as a member, that nested group will appear as a `group-member` with `subjectKind: "group"`, but its members will not be automatically included. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your security groups alongside their members. **Learn more:** * [Azure DevOps group object structure](https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/groups/list?view=azure-devops-rest-7.1\&tabs=HTTP) * [Azure DevOps membership object structure](https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/list?view=azure-devops-rest-7.1\&tabs=HTTP) ## Wiki pages[​](#wiki-pages "Direct link to Wiki pages") The following example demonstrates how to ingest Azure DevOps wiki pages to Port. Project wikis are backed by a dedicated Git repository and support real-time updates through `git.push` webhook events. You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsProject", "title": "Project", "icon": "AzureDevops", "schema": { "properties": { "state": { "title": "State", "type": "string", "icon": "AzureDevops", "description": "The current lifecycle state of the project." }, "revision": { "title": "Revision", "type": "string", "icon": "AzureDevops", "description": "The revision number, indicating how many times the project configuration has been updated." }, "visibility": { "title": "Visibility", "type": "string", "icon": "AzureDevops", "description": "Indicates whether the project is private or public" }, "defaultTeam": { "title": "Default Team", "type": "string", "icon": "Team", "description": "Default Team of the project" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "AzureDevops", "description": "Link to azure devops project" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Wiki page blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsWiki", "title": "Wiki Page", "icon": "AzureDevops", "schema": { "properties": { "path": { "title": "Path", "type": "string", "description": "The path of the wiki page." }, "wikiName": { "title": "Wiki Name", "type": "string", "description": "The name of the wiki this page belongs to." }, "wikiType": { "title": "Wiki Type", "type": "string", "description": "Whether this is a project wiki or code wiki.", "enum": ["projectWiki", "codeWiki"] }, "order": { "title": "Order", "type": "number", "description": "The display order of the page relative to siblings." }, "gitItemPath": { "title": "Git Item Path", "type": "string", "description": "Path to the markdown file in the backing git repository." }, "url": { "title": "URL", "type": "string", "format": "url", "description": "Web URL to view the wiki page." }, "content": { "title": "Content", "type": "string", "description": "Markdown content of the wiki page. Only populated when includeContent is enabled in the selector." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "azureDevopsProject", "title": "Project", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: wiki selector: query: 'true' wikiType: projectWiki port: entity: mappings: identifier: .__wiki.id + "/" + (.id | tostring) title: .path | split("/") | last blueprint: '"azureDevopsWiki"' properties: path: .path wikiName: .__wiki.name wikiType: .__wiki.type order: .order gitItemPath: .gitItemPath content: .content url: .remoteUrl relations: project: .__project.id | gsub(" "; "") ``` The `wikiType` selector filters which wikis are ingested. Set it to `projectWiki` to ingest only project wikis, or `codeWiki` for code wikis. If omitted, both types are ingested. To include the markdown content of each page, set `includeContent` to `true` in the selector. This makes an additional API call per page, so leave it disabled for large wikis unless you need the content. To filter by wiki type and include page content, add the following selector fields to your wiki mapping: ``` - kind: wiki selector: query: 'true' wikiType: projectWiki includeContent: true ``` Learn more * See the [Azure DevOps Wiki Pages Batch API](https://learn.microsoft.com/en-us/rest/api/azure/devops/wiki/pages%20batch/get?view=azure-devops-rest-7.1) for the wiki page object structure. * See the [Azure DevOps Wiki API](https://learn.microsoft.com/en-us/rest/api/azure/devops/wiki/wikis/list?view=azure-devops-rest-7.1) for the wiki object structure. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The above examples shows a specific use cases, but Port's Azure Devops integration supports the ingestion of many other Azure Devops objects, to adapt the examples above, use the Azure Devops API reference to learn about the available fields for the different supported objects: * [`repository`](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/repositories/list?view=azure-devops-rest-7.2\&tabs=HTTP#gitrepository) * [`branch`](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/refs/list?view=azure-devops-rest-7.1#gitref) * [`repository-policy`](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/policy-configurations/get?view=azure-devops-rest-7.1#policyconfiguration) * [`project`](https://learn.microsoft.com/en-us/rest/api/azure/devops/core/projects/list?view=azure-devops-rest-7.1\&tabs=HTTP#teamprojectreference) * [`pull-request`](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/pull-requests/get-pull-requests?view=azure-devops-rest-7.1\&tabs=HTTP#gitpullrequest) * [`pipeline`](https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/pipelines/list?view=azure-devops-rest-7.2#pipeline) * [`build`](https://learn.microsoft.com/en-us/rest/api/azure/devops/build/builds/list?view=azure-devops-rest-7.1\&tabs=HTTP#build) * [`pipeline-stage`](https://learn.microsoft.com/en-us/rest/api/azure/devops/build/timeline/get?view=azure-devops-rest-7.1) (`Build timeline`) * [`pipeline-run`](https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/runs/list?view=azure-devops-rest-7.1\&tabs=HTTP#run) * [`user`](https://learn.microsoft.com/en-us/rest/api/azure/devops/memberentitlementmanagement/user-entitlements/list?view=azure-devops-rest-4.1\&tabs=HTTP) * [`work-item`](https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/wiql/query-by-wiql?view=azure-devops-rest-7.1\&tabs=HTTP) * [`area-path`](https://learn.microsoft.com/en-us/rest/api/azure/devops/wit/classification-nodes/get-classification-nodes) * [`board`](https://learn.microsoft.com/en-us/rest/api/azure/devops/work/boards/list?view=azure-devops-rest-7.1) * [`release`](https://learn.microsoft.com/en-us/rest/api/azure/devops/release/releases?view=azure-devops-rest-7.1) * [`release-definition`](https://learn.microsoft.com/en-us/rest/api/azure/devops/release/definitions/list?view=azure-devops-rest-7.1) * [`environment`](https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/environments/list?view=azure-devops-rest-7.1\&tabs=HTTP) * [`release-deployment`](https://learn.microsoft.com/en-us/rest/api/azure/devops/release/deployments/list?view=azure-devops-rest-7.1\&tabs=HTTP) * [`pipeline-deployment`](https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/environmentdeployment-records/list?view=azure-devops-rest-7.1) (`Environment deployment records`) * [`file`](https://learn.microsoft.com/en-us/rest/api/azure/devops/git/items/get-items-batch?view=azure-devops-rest-7.1\&tabs=HTTP) * [`advanced-security-alert`](https://learn.microsoft.com/en-us/rest/api/azure/devops/advancedsecurity/alerts/list?view=azure-devops-rest-7.2\&tabs=HTTP) * [`group`](https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/groups/list?view=azure-devops-rest-7.1\&tabs=HTTP) * [`group-member`](https://learn.microsoft.com/en-us/rest/api/azure/devops/graph/memberships/list?view=azure-devops-rest-7.1\&tabs=HTTP) * [`wiki`](https://learn.microsoft.com/en-us/rest/api/azure/devops/wiki/pages%20batch/get?view=azure-devops-rest-7.1) When adding the ingestion of other resources, remember to add an entry to the `resources` array and change the value provided to the `kind` key accordingly. --- # GitOps Port's Azure DevOps integration makes it possible to manage Port entities with a GitOps approach, making your code projects into the source of truth for the various infrastructure assets you want to manage. ## Common use cases[​](#common-use-cases "Direct link to Common use cases") * Use Azure DevOps as the source-of-truth for your **microservices**, **projects**, **packages**, **libraries** and other software catalog assets. * Allow developers to keep the catalog up-to-date, by making updates to files in their Git projects. * Create a standardized way to document software catalog assets in your organization. ## Managing entities using GitOps[​](#managing-entities-using-gitops "Direct link to Managing entities using GitOps") To manage entities using GitOps, you will need to add a `port.yml` file to the **default branch** (usually `main`) of your project. The `port.yml` file can specify one or more Port entities that will be ingested to Port, and any change made to the `port.yml` file will also be reflected inside Port. This configuration turns your projects to the source-of-truth for the software catalog. ### GitOps `port.yml` file[​](#gitops-portyml-file "Direct link to gitops-portyml-file") The `port.yml` file is how you specify your Port entities that are managed using GitOps and whose data is ingested from your Git projects. Here are examples for valid `port.yml` files: * Single entity * Multiple entities ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ``` - identifier: myEntity1 title: My Entity1 blueprint: myFirstBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 - identifier: myEntity title: My Entity2 blueprint: mySecondBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ``` Since both of the valid `port.yml` formats follow the same structure, the following section will explain the format based on the single entity example. ### `port.yml` structure[​](#portyml-structure "Direct link to portyml-structure") Here is an example `port.yml` file: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` * The `identifier` key is used to specify the identifier of the entity that the app will create and keep up-to-date when changes occur: ``` identifier: myEntity title: My Entity ... ``` * The `title` key is used to specify the title of the entity: ``` identifier: myEntity title: My Entity ... ``` * The `blueprint` key is used to specify the identifier of the blueprint to create this entity from: ``` ... title: My Entity blueprint: myBlueprint ... ``` * The `properties` key is used to map the values to the different properties of the entity: ``` ... title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ... ``` * The `relations` key is used to map target entities to the different relations of the entity: * Single relation * Many relation ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity ``` ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ### Setting null properties[​](#setting-null-properties "Direct link to Setting null properties") When you want to clear a property's value in Port, you can explicitly set it to `null` in your `port.yml` file.
For example: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: description: null owner: null ``` Difference between null and omitting When you omit a property entirely from the `port.yml`, Port will keep its existing value. Setting a property to `null` explicitly tells Port to clear that property's value. ### Ingesting project file contents[​](#ingesting-project-file-contents "Direct link to Ingesting project file contents") It is possible to use the contents of files in the repository as the value for entity properties using a simple reference. The following example will read the string contents of `~/module1/README.md` and upload it to `myStringProp` of the specified entity. Repository folder structure used for the example: ``` root | +- port.yml | +-+ module1 | | | +- README.md | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: myStringProp: file://module1/README.md ``` #### Using relative paths[​](#using-relative-paths "Direct link to Using relative paths") It is also possible to use paths relative to the location of the `port.yml` spec file. For example: `file://./` is used to reference a file in the same directory as the `port.yml` file. `file://../` is used to reference a file that is one directory above and so on. The following example reads `README.md` and `module1/requirements.txt` using paths relative to `port.yml` Repository folder structure used for the example: ``` root | +-+ meta | | | +-- port.yml | | | +-+ README.md | +-+ module1 | | | +- requirements.txt | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: readme: file://./README.md module1Requirements: file://../module1/requirements.txt ``` ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Port.yml ingestion pattern[​](#portyml-ingestion-pattern "Direct link to Port.yml ingestion pattern") The `port.yml` ingestion pattern allows you to configure the Azure DevOps integration to ingest `port.yml` files as part of the resync process. This approach is particularly useful when you want to maintain data integrity and ensure that your `port.yml` files are properly synchronized with Port. `port.yml` files operate in the GitOps methodology, meaning they are ingested into Port whenever a commit to the main branch of the repository is detected. Here's how to configure the integration to parse and ingest `port.yml` files: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "port.yml" port: entity: mappings: identifier: .file.content.parsed.identifier title: .file.content.parsed.title blueprint: .file.content.parsed.blueprint properties: property_a: .file.content.parsed.properties.property_a property_b: .file.content.parsed.properties.property_b relations: relation_a: .file.content.parsed.relations.relation_a relation_b: .file.content.parsed.relations.relation_b ``` For multiple entities in a single `port.yml` file, use the `itemsToParse` key: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "port.yml" port: itemsToParse: .file.content.parsed entity: mappings: identifier: .item.identifier title: .item.title blueprint: .item.blueprint properties: property_a: .item.properties.property_a property_b: .item.properties.property_b relations: relation_a: .item.relations.relation_a relation_b: .item.relations.relation_b ``` ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/advanced/.md) page for advanced use cases and configurations. --- # Mapping Extensions ## Introduction[​](#introduction "Direct link to Introduction") The default way to map your data to Port is by using [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform your data to Port entities. However, in some cases you may want to map data to Port in a way that default JQ mapping is not enough. Possible Use Cases: * Map your repository README.md file contents into Port. * Check if a specific file exists in your repository. * Check if a specific string exists in your repository. * Check if a specific version of a package is used in your repository. * Check if a CI/CD pipeline is configured in your repository. ## Mapping file content into Port[​](#mapping-file-content-into-port "Direct link to Mapping file content into Port") The following example demonstrates how to define and export your Azure Devops projects and their **README.md** file contents to Port: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "azureDevopsRepository", "title": "Service", "icon": "AzureDevops", "schema": { "properties": { "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link" }, "readme": { "title": "README", "type": "string", "format": "markdown", "icon": "Book" }, "workItemLinking": { "title": "Work Item Linking", "default": false, "type": "boolean" }, "minimumApproverCount": { "title": "Minimum Approver Count", "default": 0, "type": "number" }, "slack": { "icon": "Slack", "type": "string", "title": "Slack", "format": "url" }, "tier": { "title": "Tier", "type": "string", "description": "How mission-critical the service is", "enum": [ "Mission Critical", "Customer Facing", "Internal Service", "Other" ], "enumColors": { "Mission Critical": "turquoise", "Customer Facing": "green", "Internal Service": "darkGray", "Other": "yellow" }, "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": { "defaultTeam": { "title": "Default Team", "path": "project.defaultTeam" } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "azureDevopsProject", "required": false, "many": false } } } ``` As we can see one of the properties is of type markdown, this means that we need to map the **README.md** file contents into Port. To do so, we will use the `includedFiles` selector to specify which files to fetch, and then reference them in the mapping using `.__includedFiles[""]`. Deprecation notice The `file://` prefix is deprecated and will be removed in a future version. Use the `includedFiles` selector instead.
The `file://` prefix will continue to work but will show deprecation warnings in the logs. ``` - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .id title: .name blueprint: '"service"' properties: url: .remoteUrl readme: .__includedFiles["README.md"] ``` ## Link pipelines to repositories via selector[​](#link-pipelines-to-repositories-via-selector "Direct link to Link pipelines to repositories via selector") You can configure your selector to include repository information in the pipeline entity mapping. This allows you to create a direct relationship between a pipeline and its source repository. ``` - kind: pipeline selector: query: 'true' includeRepo: 'true' port: entity: mappings: identifier: ."__projectId" + "/" + (.id | tostring) title: .name blueprint: '"azureDevopsPipeline"' properties: url: .url revision: .revision folder: .folder relations: project: .__projectId | gsub(" "; "") repository: if .__repository then .__repository.id else null end ``` Recommendation Use this only when necessary, as including repository data requires an extra API call per pipeline, which increases the number of requests made and can impact your Azure DevOps API rate limits. If you don’t require repo-level linkage, it’s more efficient to relate pipelines → projects instead. --- # Bitbucket app (deprecated) Port's Bitbucket App integration allows you to model Bitbucket resources in your software catalog and ingest data into them. Deprecation Notice This app will be deprecated in the future and support for the app will be discontinued soon. To integrate Port with Bitbucket Cloud, we recommend using the [Bitbucket Cloud integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/.md). This documentation covers Port's integration with **Bitbucket Cloud**. For information about integrating with Bitbucket Server (Self-Hosted), please refer to the [Bitbucket Server integration documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/.md). ## Overview[​](#overview "Direct link to Overview") This integration allows you to: * Map and organize your desired Bitbucket resources and their metadata in Port (see supported resources below). * Watch for Bitbucket object changes (create/update/delete) in real-time, and automatically apply the changes to your software catalog. * Manage Port entities using GitOps. ### Supported resources[​](#supported-resources "Direct link to Supported resources") The resources that can be ingested from Bitbucket into Port are listed below.
It is possible to reference any field that appears in the API responses linked below in the mapping configuration. * [`repository`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-get) * [`pull-request`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-pullrequests-pull-request-id-get) * [`folder`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get) ## Setup[​](#setup "Direct link to Setup") To install Port's Bitbucket app, follow these steps: 1. Go to the [Bitbucket app installation page](https://marketplace.atlassian.com/apps/1229886/port-connector-for-bitbucket?hosting=cloud\&tab=overview). 2. Click `Get it now`. 3. Select your desired workspace and click on `Grant access`. 4. Once the installation has finished, you will be redirected to Port. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: repository selector: query: 'true' port: entity: mappings: identifier: .name title: .name blueprint: '"bitbucketRepository"' properties: url: .links.html.href defaultBranch: .main_branch last_activity: .updated_on - kind: pull-request selector: query: 'true' port: entity: mappings: identifier: .destination.repository.name + (.id|tostring) title: .title blueprint: '"bitbucketPullRequest"' properties: status: .state createdAt: .created_on updatedAt: .updated_on link: .links.html.href relations: repository: .destination.repository.name bitbucket_creator: .author.display_name | gsub(" ";"_") bitbucket_assignees: .participants | map(.user.display_name) | map(gsub(" ";"_")) bitbucket_reviewers: .reviewers | map(.display_name) | map(gsub(" ";"_")) service: combinator: '"and"' rules: - operator: '"="' property: '"bitbucket_repository"' value: .destination.repository.name creator: combinator: '"and"' rules: - operator: '"="' property: '"bitbucket_username"' value: .author.display_name | gsub(" ";"_") assignees: combinator: '"and"' rules: - operator: '"in"' property: '"bitbucket_username"' value: .participants | map(.user.display_name) | map(gsub(" ";"_")) reviewers: combinator: '"and"' rules: - operator: '"in"' property: '"bitbucket_username"' value: .reviewers | map(.display_name) | map(gsub(" ";"_")) ``` To ingest Bitbucket objects, use one of the following methods: * Using Port's UI * Using Bitbucket To manage your Bitbucket integration configuration using Port: 1. Go to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Under `Exporters`, click on your desired BitBucket organization. 3. A window will open containing the default YAML configuration of your BitBucket integration. 4. Here you can modify the configuration to suit your needs, by adding/removing entries. 5. When finished, click `resync` to apply any changes. Using this method applies the configuration to all repositories in your Bitbucket workspace. When configuring the integration **using Port**, the YAML configuration is global, allowing you to specify mappings for multiple Port blueprints. To manage your Bitbucket integration configuration using a config file in BitBucket: 1. Go to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Under `Exporters`, click on your desired BitBucket organization. 3. A window will open containing the default YAML configuration of your GitHub integration. 4. Scroll all the way down, and turn on the `Manage this integration using the "port-app-config.yml" file` toggle. This will clear the configuration in Port's UI. When configuring the integration **using BitBucket**, you can choose either a global or granular configuration: * **Global configuration:** create a `.bitbucket-private` repository in your workspace and add the `port-app-config.yml` file to the repository. * Using this method applies the configuration to all repositories in your Bitbucket workspace (unless it is overridden by a granular `port-app-config.yml` in a repository). * **Granular configuration:** add the `port-app-config.yml` file to the root of your desired repository. * Using this method applies the configuration only to the repository where the `port-app-config.yml` file exists. When using global configuration **using Bitbucket**, the configuration specified in the `port-app-config.yml` file will only be applied if the file is in the **default branch** of the repository (usually `main`). Important When **using Port's UI**, the specified configuration will override any `port-app-config.yml` file in your BitBucket repository/ies. ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Ingesting Git objects[​](#ingesting-git-objects "Direct link to Ingesting Git objects") By using Port's Bitbucket app, you can automatically ingest Bitbucket resources into Port based on real-time events. The app allows you to ingest a variety of objects resources provided by the Bitbucket API, including repositories, pull requests and more. It also allows you to perform "extract, transform, load (ETL)" on data from the Bitbucket API into the desired software catalog data model. The Bitbucket app uses a YAML configuration file to describe the ETL process to load data into the developer portal. The approach reflects a golden middle between an overly opinionated Git visualization that might not work for everyone and a too-broad approach that could introduce unneeded complexity into the developer portal. After installing the app, Port will automatically create a `repository` blueprint in your catalog (representing a BitBucket repository), along with a default YAML configuration file that defines where the data fetched from BitBucket's API should go in the blueprint. ## Permissions[​](#permissions "Direct link to Permissions") Port's Bitbucket integration requires the following scopes: * `account`. * `repository`. * `pullrequest`. Default permissions You will be prompted to confirm these permissions when first installing the App. ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/examples/.md) page for practical configurations and their corresponding blueprint definitions. ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=BitBucket). ## GitOps[​](#gitops "Direct link to GitOps") Port's Bitbucket app also provides GitOps capabilities, refer to the [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/gitops/.md) page to learn more. ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/advanced/.md) page for advanced use cases and examples. --- # Advanced The Bitbucket integration supports additional flags to provide additional configuration, making it easier to configure its behavior to your liking. ## Using advanced configurations[​](#using-advanced-configurations "Direct link to Using advanced configurations") The following advanced configuration parameters are available: * Spec path * Delete dependent entities * Enable merge entity * Create missing related entities The `specPath` parameter specifies a string that Port's Bitbucket app will use when constructing search paths leading to a `yml` file, every path in the repository which ends with the `specPath` value will be scanned. * **Default value**: `port.yml` * **Use case**: * If you want the app to scan a different file than `port.yml` (for example, change configure the app to scan files named `my-port-config.yml` using the pattern `my-port-config.yml`). * If you want the app to ignore `port.yml` files in certain paths. The `deleteDependentEntities` parameter is used to enable deletion of dependent Port entities. This is useful when you have two blueprints with a required relation, and the target entity in the relation should be deleted. In this scenario, the delete operation will fail if this flag is set to `false` if the flag is set to `true`, the source entity will be deleted as well. * **Default value**: `false` (disabled) * **Use case**: Deletion of dependent Port entities. Must be enabled, if you want to delete a target entity (and its source entities) in a required relation. The `enableMergeEntity` parameter specifies whether to use the [create/update](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-update#usage) or [create/override](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-override#usage) strategy when creating entities listed in a `port.yml` file. * **Default value**: `true` (use create/update) * **Use case**: use `false` if you want Bitbucket to be the source-of-truth for catalog entities. Use `true` if you want to use Bitbucket as the source for some properties of entities in the catalog, and use other sources to for properties which are subject to change automatically. The `createMissingRelatedEntities` parameter is used to enable the creation of missing related Port entities automatically in cases where the target related entity does not exist in the software catalog yet. * **Default value**: `true` to allow the Bitbucket app to create barebones related entities, in case those related entities do not exist in the software catalog. * **Use case**: use `false` if you do not want this default behavior (do not create missing related entities). All of the advanced configurations listed above can be added to the [`port-app-config.yml`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/.md#port-app-configyml-file) file. --- # Examples ## Mapping repositories, file contents and pull requests[​](#mapping-repositories-file-contents-and-pull-requests "Direct link to Mapping repositories, file contents and pull requests") The following example demonstrates how to ingest your Bitbucket repositories, their README.md file contents and pull requests to Port.
You can use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "bitbucketRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Pull request blueprint (click to expand)** Create in Port ``` { "identifier": "bitbucketPullRequest", "title": "Pull Request", "icon": "GitVersion", "schema": { "properties": { "creator": { "title": "Creator", "type": "string", "format": "user" }, "assignees": { "title": "Assignees", "type": "array" }, "reviewers": { "title": "Reviewers", "type": "array" }, "status": { "title": "Status", "type": "string", "enum": ["MERGED","OPEN","DECLINED"], "enumColors": { "MERGED": "purple", "OPEN": "green", "DECLINED": "red" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "link": { "title": "Link", "format": "url", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "lead_time_days": { "title": "Lead time (Days)", "calculation": "if .properties.status == \"MERGED\" then ((.properties.updatedAt | sub(\"\\\\.[0-9]+\\\\+00:00$\"; \"Z\") | strptime(\"%Y-%m-%dT%H:%M:%SZ\") | mktime) - (.properties.createdAt | sub(\"\\\\.[0-9]+\\\\+00:00$\"; \"Z\") | strptime(\"%Y-%m-%dT%H:%M:%SZ\") | mktime)) / 86400 | tonumber else null end", "type": "number" } }, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "bitbucketRepository", "required": false, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: ".name" # The Entity identifier will be the repository name. After the Entity is created, the exporter will send `PATCH` requests to update this microservice within Port. title: ".name" blueprint: '"bitbucketRepository"' properties: readme: file://README.md # fetching the README.md file that is within the root folder of the repository and ingesting its contents as a markdown property url: ".links.html.href" defaultBranch: .main_branch - kind: pull-request selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: ".destination.repository.name + (.id|tostring)" # The Entity identifier will be the repository name + the pull request ID. After the Entity is created, the exporter will send `PATCH` requests to update this pull request within Port. title: ".title" blueprint: '"bitbucketPullRequest"' properties: creator: ".author.display_name" assignees: "[.participants[].user.display_name]" reviewers: "[.reviewers[].user.display_name]" status: ".state" createdAt: ".created_on" updatedAt: ".updated_on" link: ".links.html.href" relations: repository: ".destination.repository.name" ``` Bitbucket API reference * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/.md#setup) section to learn more about the `port-app-config.yml` setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform Bitbucket objects to Port Entities. * Click [Here](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-get) for the Bitbucket repository object structure. * Click [Here](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-pullrequests-pull-request-id-get) for the Bitbucket pull request object structure. After creating the blueprints and committing the `port-app-config.yml` file to your `.bitbucket-private` or to a specific repository, you will see new entities in Port matching your repositories alongside their README.md file contents and pull requests. (Remember that the `port-app-config.yml` file has to be in the **default branch** of the repository to take effect). ## Mapping repositories and monorepos[​](#mapping-repositories-and-monorepos "Direct link to Mapping repositories and monorepos") The following example demonstrates how to ingest your Bitbucket repositories and their folders to Port. By following this example you can map your different repositories, packages and libraries from your monorepo into separate entities in Port. you may use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "bitbucketRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: apps/* # Relative path to the folders within the repositories. repos: # List of repositories to include folders from. - backend-service - frontend-service port: entity: mappings: identifier: .folder.name blueprint: '"bitbucketRepository"' properties: url: .repo.links.html.href + "/src/" + .repo.mainbranch.name + "/" + .folder.path readme: file://README.md ``` Monorepo API reference * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/.md#setup) section to learn more about the `port-app-config.yml` setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitHub objects to Port Entities. * Click [Here](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-get) for the Bitbucket repository object structure. * Click [Here](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get) for the Bitbucket folder object structure. ## Mapping supported resources[​](#mapping-supported-resources "Direct link to Mapping supported resources") The above example shows a specific use case, but Port's Bitbucket app supports the ingestion of many other Bitbucket objects, to adapt the example above, use the Bitbucket API reference to learn about the available fields for the different supported objects: * [`repository`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-get) * [`pull-request`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-pullrequests-pull-request-id-get) * [`folder`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get) When adding the ingestion of other resources, remember to add a entry to the `resources` array and change the value provided to the `kind` key accordingly. --- # GitOps Port's Bitbucket integration makes it possible to manage Port entities with a GitOps approach, making your code repositories into the source of truth for the various infrastructure assets you want to manage. Bitbucket Server (Self-Hosted) This documentation covers GitOps for **Bitbucket App**. For information about GitOps with Bitbucket Server (Self-Hosted), please refer to the [Bitbucket Server GitOps documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/gitops/.md). ## 💡 Bitbucket GitOps common use cases[​](#-bitbucket-gitops-common-use-cases "Direct link to 💡 Bitbucket GitOps common use cases") * Use Bitbucket as the source-of-truth for your **microservices**, **packages**, **libraries** and other software catalog assets. * Allow developers to keep the catalog up-to-date, by making updates to files in their Git repositories. * Create a standardized way to document software catalog assets in your organization. ## Managing entities using GitOps[​](#managing-entities-using-gitops "Direct link to Managing entities using GitOps") To manage entities using GitOps, you will need to add a `port.yml` file to the **default branch** (usually `main`) of your repository. Note that the `port.yml` file is not the same as the [`port-app-config.yml` file](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/.md#port-app-configyml-file) used to configure the BitBucket integration, and does not replace it. The `port.yml` file can specify one or more Port entities that will be ingested to Port, and any change made to the `port.yml` file will also be reflected inside Port. This configuration turns your Bitbucket repositories to the source-of-truth for the software catalog. Existing port.yml needs manual push To manage entities using GitOps and the `port.yml` file, the app listens to a `push` event sent from the Git provider. This means that if the `port.yml` file exists in the repository before installing the Git app, that file will not be picked up automatically. You will need to make some update to the `port.yml` file and push it to the repository in order for the Git app to properly track and ingest the entity information. ### GitOps `port.yml` file[​](#gitops-portyml-file "Direct link to gitops-portyml-file") The `port.yml` file is how you specify your Port entities that are managed using GitOps and whose data is ingested from your Git repositories. Here are examples for valid `port.yml` files: * Single entity * Multiple entities ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ``` - identifier: myEntity1 title: My Entity1 blueprint: myFirstBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 - identifier: myEntity title: My Entity2 blueprint: mySecondBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ``` Since both of the valid `port.yml` formats follow the same structure, the following section will explain the format based on the single entity example. ### `port.yml` structure[​](#portyml-structure "Direct link to portyml-structure") Here is an example `port.yml` file: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` * The `identifier` key is used to specify the identifier of the entity that the app will create and keep up-to-date when changes occur: ``` identifier: myEntity title: My Entity ... ``` * The `title` key is used to specify the title of the entity: ``` identifier: myEntity title: My Entity ... ``` * The `blueprint` key is used to specify the identifier of the blueprint to create this entity from: ``` ... title: My Entity blueprint: myBlueprint ... ``` * The `properties` key is used to map the values to the different properties of the entity: ``` ... title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ... ``` * The `relations` key is used to map target entities to the different relations of the entity: * Single relation * Many relation ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity ``` ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ### Ingesting repository file contents[​](#ingesting-repository-file-contents "Direct link to Ingesting repository file contents") It is possible to use the contents of files in the repository as the value for entity properties using a simple reference. The following example will read the string contents of `~/module1/README.md` and upload it to `myStringProp` of the specified entity. Repository folder structure used for the example: ``` root | +- port.yml | +-+ module1 | | | +- README.md | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: myStringProp: file://module1/README.md ``` #### Using relative paths[​](#using-relative-paths "Direct link to Using relative paths") It is also possible to use paths relative to the location of the `port.yml` spec file. For example: `file://./` is used to reference a file in the same directory as the `port.yml` file. `file://../` is used to reference a file that is one directory above and so on. The following example reads `README.md` and `module1/requirements.txt` using paths relative to `port.yml` Repository folder structure used for the example: ``` root | +-+ meta | | | +-- port.yml | | | +-+ README.md | +-+ module1 | | | +- requirements.txt | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: readme: file://./README.md module1Requirements: file://../module1/requirements.txt ``` ## Examples[​](#examples "Direct link to Examples") Check out the [example repository](https://github.com/port-labs/github-app-setup-example) for a microservice blueprint and a matching `port.yml` file which specifies a microservice entity. ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Port.yml ingestion pattern[​](#portyml-ingestion-pattern "Direct link to Port.yml ingestion pattern") The `port.yml` ingestion pattern allows you to configure the Bitbucket integration to ingest `port.yml` files as part of the resync process. This approach is particularly useful when you want to maintain data integrity and ensure that your `port.yml` files are properly synchronized with Port. `port.yml` files operate in the GitOps methodology, meaning they are ingested into Port whenever a commit to the main branch of the repository is detected. Here's how to configure the integration to parse and ingest `port.yml` files: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: entity: mappings: identifier: .file.content.identifier title: .file.content.title blueprint: .file.content.blueprint properties: property_a: .file.content.properties.property_a property_b: .file.content.properties.property_b relations: relation_a: .file.content.relations.relation_a relation_b: .file.content.relations.relation_b ``` For multiple entities in a single `port.yml` file, use the `itemsToParse` key: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: itemsToParse: .file.content entity: mappings: identifier: .item.identifier title: .item.title blueprint: .item.blueprint properties: property_a: .item.properties.property_a property_b: .item.properties.property_b relations: relation_a: .item.relations.relation_a relation_b: .item.relations.relation_b ``` ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-app/advanced/.md) page for advanced use cases and configurations. --- # Bitbucket Cloud ![BitBucket icon](/img/guides/icons/BitBucket.svg)![BitBucket icon](/img/guides/icons/BitBucket.svg) Loading version... Port's Bitbucket integration allows you to model Bitbucket cloud resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-workspace-projects-get)[Repository](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-get)[Pull Request](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-pullrequests-pull-request-id-get)[Folder](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get)[File](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get) ## Core support [Live eventsSee supported resources](#set-up-live-events) Bitbucket Server (Self-Hosted) This documentation covers Port's integration with **Bitbucket Cloud**. For information about integrating with Bitbucket Server (Self-Hosted), please refer to the [Bitbucket Server integration documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/.md). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A Bitbucket Cloud account with admin privileges to the workspace you want to ingest data from. * If you choose the real time & always on installation method, you will need a kubernetes cluster on which to install the integration. * Your Port user role is set to `Admin`. ## Setup[​](#setup "Direct link to Setup") ### Create authentication credentials[​](#create-authentication-credentials "Direct link to Create authentication credentials") App Password Deprecation Bitbucket is deprecating app passwords. As of September 9, 2025, new app passwords cannot be created, and existing ones will be disabled on June 9, 2026. **Use user-scoped tokens or workspace tokens instead.** The integration supports three authentication methods: * **Workspace Token** (Recommended for Premium accounts) * Most scalable option for large organizations * Supports multiple tokens (comma-separated) to distribute load and avoid rate limits * [Create a workspace token](https://support.atlassian.com/bitbucket-cloud/docs/workspace-access-tokens/) * **Note:** Workspace tokens are a Bitbucket Premium feature * **Required scopes:** * `workspace`: `read` * `project`: `read` * `repository`: `read` * `pullrequest`: `read` * `webhooks`: `read` and `write` * **User-Scoped Token** (Replacement for App Passwords) * **Required** for new setups using the `file` kind (existing app passwords also work, but can't be created anymore) * Works in free Bitbucket environments * Requires user's email address for authentication * [Create a user-scoped token](https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/) * **Required scopes:** * Read: `read:repository:bitbucket`, `read:project:bitbucket`, `read:pullrequest:bitbucket`, `read:webhook:bitbucket`, `read:workspace:bitbucket` * Write: `write:webhook:bitbucket` * **App Password** (Deprecated) * ⚠️ Being phased out by Bitbucket - migrate to user-scoped tokens * Requires username and app password combination * [Create an app password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) * **Required scopes:** * `workspace`: `read` * `project`: `read` * `repository`: `read` * `pullrequest`: `read` * `webhooks`: `read` and `write` Use of dedicated accounts and tokens We recommend using multiple workspace tokens for the integration, as this provides better security and scalability. Using multiple tokens helps distribute the load and avoid rate limiting issues. If using user-scoped tokens or app passwords, consider using a dedicated account, as credentials from the same Bitbucket account share rate limits.
Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` resources: - kind: project selector: query: 'true' port: entity: mappings: identifier: .uuid | gsub("[{-}]"; "") title: .name blueprint: '"bitbucketProject"' properties: private: .is_private description: .description type: .type url: .links.html.href - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"bitbucketRepository"' properties: url: .links.html.href defaultBranch: .mainbranch.name readme: .__includedFiles["README.md"] relations: project: .project.uuid | gsub("[{-}]"; "") - kind: pull-request selector: query: 'true' port: entity: mappings: identifier: .destination.repository.name + (.id|tostring) title: .title blueprint: '"bitbucketPullRequest"' properties: creator: .author.display_name assignees: '[.participants[].user.display_name]' reviewers: '[.reviewers[].user.display_name]' status: .state createdAt: .created_on updatedAt: .updated_on link: .links.html.href relations: repository: .destination.repository.name ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view the mapping and blueprint definition for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/examples/.md). ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Cloud 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 Cloud 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Bitbucket Cloud is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Bitbucket Cloud can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/examples/.md) page for practical configurations and their corresponding blueprint definitions. ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=BitBucket). ## GitOps[​](#gitops "Direct link to GitOps") Port's Bitbucket integration also provides GitOps capabilities, refer to the [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/gitops/.md) page to learn more. ## Advanced[​](#advanced "Direct link to Advanced") For advanced configuration options and use cases, refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/advanced/.md) page. --- # Advanced The Bitbucket integration supports additional flags to provide additional configuration, making it easier to configure its behavior to your liking. ## Using advanced configurations[​](#using-advanced-configurations "Direct link to Using advanced configurations") The following advanced configuration parameters are available: * Delete dependent entities * Create missing related entities The `deleteDependentEntities` parameter is used to enable deletion of dependent Port entities. This is useful when you have two blueprints with a required relation, and the target entity in the relation should be deleted. In this scenario, the delete operation will fail if this flag is set to `false` if the flag is set to `true`, the source entity will be deleted as well. * **Default value**: `false` (disabled) * **Use case**: Deletion of dependent Port entities. Must be enabled, if you want to delete a target entity (and its source entities) in a required relation. The `createMissingRelatedEntities` parameter is used to enable the creation of missing related Port entities automatically in cases where the target related entity does not exist in the software catalog yet. * **Default value**: `true` to allow the Bitbucket app to create barebones related entities, in case those related entities do not exist in the software catalog. * **Use case**: use `false` if you do not want this default behavior (do not create missing related entities). All of the advanced configurations listed above can be added to the mapping on the Connectors page. --- # Capabilities ## Ingest Git objects[​](#ingest-git-objects "Direct link to Ingest Git objects") By using Port's Bitbucket integration, you can automatically ingest Bitbucket resources into Port based on real-time events. The integration allows you to ingest a variety of objects resources provided by the Bitbucket API, including repositories, pull requests and files. It also allows you to perform "extract, transform, load (ETL)" on data from the Bitbucket API into the desired software catalog data model. When you install the integration, Port will automatically create a `bitbucketProject`, `bitbucketRepository` and `bitbucketPullRequest` blueprints in your catalog (representing a BitBucket project, repository and pull request respectively). The YAML configuration mapping will also be added to the [connectors](https://app.port.io/settings/data-sources) page in Port where you can manage the integration. ## Ingest files from your repositories[​](#ingest-files-from-your-repositories "Direct link to Ingest files from your repositories") User-Scoped Token Required The `file` kind requires a **user-scoped token** for authentication to access Bitbucket's file/code search API. While existing app passwords also work, **new app passwords cannot be created as of September 9, 2025**. Workspace tokens do not support file ingestion. Port allows you to fetch `JSON` and `YAML` files from your repositories, and create entities from them in your software catalog. This is done using the `file` kind in your Bitbucket mapping configuration. Changes to the files are tracked through repository `push` events and periodic resyncs. For example, say you want to manage your `package.json` files in Port. One option is to create a `manifest` blueprint, with each of its entities representing a `package.json` file. The following configuration fetches all `package.json` files from "MyRepo" and "MyOtherRepo", and creates an entity for each of them, based on the `manifest` blueprint: ``` resources: # IMPORTANT: The 'file' kind requires user-scoped token (or existing app password) # Workspace tokens do not support file ingestion # New app passwords cannot be created - use user-scoped token instead - kind: file selector: query: 'true' files: # Note that glob patterns are not supported, so you can use only '*' wildcard to match multiple directories and subdirectories path: '/*' filenames: - package.json # The `repos` key can be used to filter the repositories from which the files will be fetched repos: - "MyRepo" - "MyOtherRepo" skipParsing: false port: entity: mappings: identifier: .file.path title: .file.name blueprint: '"manifest"' properties: project_name: .content.name project_version: .content.version license: .content.license ``` Test your mapping After adding the `file` kind to your mapping configuration, click on the `Resync` button. When you open the mapping configuration again, you will see real examples of files fetched from your Bitbucket organization. This will help you see what data is available to use in your `jq` expressions. Click on the `Test mapping` button to test your mapping against the example data. In any case, the structure of the available data looks like this: **Available data example (click to expand)** ``` { "content":{ "name":"my-awesome-project", "version":"1.0.0", "description":"A sample Node.js project", "main":"index.js", "scripts":{ "start":"node index.js", "test":"echo \"Error: no test specified\" && exit 1" }, "dependencies":{ "express":"^4.17.1" } }, "metadata":{ "path":"package.json", "commit":{ "hash":"fe0dce2d09e1545068b8f5b4b95807c12b49c1f4", "links":{ "self":{ "href":"https://api.bitbucket.org/2.0/repositories/port-test/port-repo-3/commit/fe0dce2d09e1545068b8f5b4b95807c12b49c1f4" }, "html":{ "href":"https://bitbucket.org/port-test/port-repo-3/commits/fe0dce2d09e1545068b8f5b4b95807c12b49c1f4" } }, "type":"commit", "repository":{ "type":"repository", "full_name":"port-test/port-repo-3", "links":{ "self":{ "href":"https://api.bitbucket.org/2.0/repositories/port-test/port-repo-3" }, "html":{ "href":"https://bitbucket.org/port-test/port-repo-3" }, "avatar":{ "href":"https://bytebucket.org/ravatar/%7B14892ea7-2241-4b96-a37a-93ab59b6479b%7D?ts=default" } }, "name":"port-repo-3", "uuid":"{14892ea7-2241-4b96-a37a-93ab59b6479b}", "mainbranch":{ "name":"new" } } }, "type":"commit_file", "links":{ "self":{ "href":"https://api.bitbucket.org/2.0/repositories/port-test/port-repo-3/src/fe0dce2d09e1545068b8f5b4b95807c12b49c1f4/package.json" } } }, "repo":{ "type":"repository", "full_name":"port-test/port-repo-3", "links":{ "self":{ "href":"https://api.bitbucket.org/2.0/repositories/port-test/port-repo-3" }, "html":{ "href":"https://bitbucket.org/port-test/port-repo-3" }, "avatar":{ "href":"https://bytebucket.org/ravatar/%7B14892ea7-2241-4b96-a37a-93ab59b6479b%7D?ts=default" } }, "name":"port-repo-3", "uuid":"{14892ea7-2241-4b96-a37a-93ab59b6479b}", "mainbranch":{ "name":"main" } }, "branch":"main" } ``` ### Create multiple entities from a single file[​](#create-multiple-entities-from-a-single-file "Direct link to Create multiple entities from a single file") In some cases, we would like to parse a single JSON/YAML file and create multiple entities from it. For this purpose, we can use the `itemsToParse` key in our mapping configuration. For example, say you want to track/manage a project's dependencies in Port. One option is to create a `package` blueprint, with each of its entities representing a dependency from a `package.json` file. The following configuration fetches a `package.json` file from a specific repository, and creates an entity for each of the dependencies in the file, based on the `package` blueprint: ``` resources: # IMPORTANT: The 'file' kind requires user-scoped token (or existing app password) # Workspace tokens do not support file ingestion # New app passwords cannot be created - use user-scoped token instead - kind: file selector: query: 'true' files: path: '/*' filenames: - package.json # Note that in this case we are fetching from a specific repository repos: - "MyRepo" skipParsing: false port: itemsToParse: .content.dependencies | to_entries entity: mappings: # Since identifier cannot contain special characters, we are using jq to remove them identifier: >- .item.key + "_" + if (.item.value | startswith("^")) then .item.value[1:] else .item.value end title: .item.key + "@" + .item.value blueprint: '"package"' properties: package: .item.key version: .item.value relations: {} ``` The `itemsToParse` key is used to specify the path to the array of items you want to parse from the file. In this case, we are parsing the `dependencies` array from the `package.json` file. Once the array is parsed, we can use the `item` key to refer to each item in the array. #### Multi-document YAML files[​](#multi-document-yaml-files "Direct link to Multi-document YAML files") For multi-document YAML files (a single file containing multiple YAML documents separated by `---`), `.content` will not resolve to an object, but to an array of objects. You can use one of these methods to ingest multi-document YAML files: 1. Use the `itemsToParse` key to create multiple entities from such a file (see example above). 2. Map the result to an `array` property. Mixed YAML types If you have both single-document and multi-document YAML files in your repositories, you can use the `itemsToParse` key like this to handle both cases: ``` itemsToParse: .content | if type== "object" then [.] else . end ``` ### Path Structure[​](#path-structure "Direct link to Path Structure") Files are referenced using paths relative to the repository root. For example: ✅ Valid paths: * `/` * `integrations/*` * `docs/` * `src/config/` * `deployment/k8s/` ❌ Invalid paths: * `/README.md` (leading slash) * `C:/repo/config.json` (absolute path) * `../other-repo/file.txt` (parent directory reference) * `**/*.yaml` (unsupported glob pattern) ### Best Practices[​](#best-practices "Direct link to Best Practices") For optimal performance and maintainability: * Limit the number of tracked files per repository * Use the `repos` selector to scope file ingestion to specific repositories * Use `skipParsing: true` for non-JSON/YAML files ### Limitations[​](#limitations "Direct link to Limitations") The following limitations apply to the file mapping feature in the Bitbucket integration: * **File Size**: Files must be 320kb or smaller to be ingested * **Path Patterns**: Only the `*` wildcard is supported in paths. Other glob patterns (e.g., `**/*.json`) are not supported * **Branch Support**: Only files from the default branch can be ingested * **Special Characters**: Special characters in filenames are automatically stripped by the API --- # Examples ## Repositories, file contents and pull requests[​](#repositories-file-contents-and-pull-requests "Direct link to Repositories, file contents and pull requests") The following example demonstrates how to ingest your Bitbucket projects, repositories, pull requests and files to Port. You will also see how to ingest folders from monorepo repositories and files from specific repositories: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "bitbucketProject", "description": "A software catalog to represent Bitbucket project", "title": "Bitbucket Project", "icon": "BitBucket", "schema": { "properties": { "private": { "icon": "DefaultProperty", "title": "Private", "type": "boolean" }, "description": { "title": "Description", "type": "string", "icon": "DefaultProperty" }, "type": { "icon": "DefaultProperty", "title": "Type", "type": "string" }, "url": { "title": "Url", "icon": "DefaultProperty", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "bitbucketRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Pull request blueprint (click to expand)** Create in Port ``` { "identifier": "bitbucketPullRequest", "title": "Pull Request", "icon": "GitVersion", "schema": { "properties": { "creator": { "title": "Creator", "type": "string", "format": "user" }, "assignees": { "title": "Assignees", "type": "array" }, "reviewers": { "title": "Reviewers", "type": "array" }, "status": { "title": "Status", "type": "string", "enum": ["MERGED","OPEN","DECLINED"], "enumColors": { "MERGED": "purple", "OPEN": "green", "DECLINED": "red" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "link": { "title": "Link", "format": "url", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "lead_time_days": { "title": "Lead time (Days)", "calculation": "if .properties.status == \"MERGED\" then ((.properties.updatedAt | sub(\"\\\\.[0-9]+\\\\+00:00$\"; \"Z\") | strptime(\"%Y-%m-%dT%H:%M:%SZ\") | mktime) - (.properties.createdAt | sub(\"\\\\.[0-9]+\\\\+00:00$\"; \"Z\") | strptime(\"%Y-%m-%dT%H:%M:%SZ\") | mktime)) / 86400 | tonumber else null end", "type": "number" } }, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "bitbucketRepository", "required": false, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: project selector: query: "true" port: entity: mappings: identifier: '.uuid | gsub("[{-}]"; "")' title: ".name" blueprint: '"bitbucketProject"' properties: private: .is_private description: ".description" type: .type url: ".links.html.href" - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md port: entity: mappings: identifier: ".name" # The Entity identifier will be the repository name. After the Entity is created, the exporter will send `PATCH` requests to update this microservice within Port. title: ".name" blueprint: '"bitbucketRepository"' properties: readme: .__includedFiles["README.md"] # fetching the README.md file that is within the root folder of the repository and ingesting its contents as a markdown property url: ".links.html.href" defaultBranch: .main_branch relations: project: '.project.uuid | gsub("[{-}]"; "")' - kind: pull-request selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: ".destination.repository.name + (.id|tostring)" # The Entity identifier will be the repository name + the pull request ID. After the Entity is created, the exporter will send `PATCH` requests to update this pull request within Port. title: ".title" blueprint: '"bitbucketPullRequest"' properties: creator: ".author.display_name" assignees: "[.participants[].user.display_name]" reviewers: "[.reviewers[].user.display_name]" status: ".state" createdAt: ".created_on" updatedAt: ".updated_on" link: ".links.html.href" relations: repository: ".destination.repository.name" ``` ### Repository configuration options[​](#repository-configuration-options "Direct link to Repository configuration options") * User role * Repository query The `userRole` selector filters repositories based on the current user’s role. Only **one** role can be specified per sync. Allowed values: * `owner`: Repositories owned by the current user. * `member`: Repositories to which the user has explicit read access. * `admin`: Repositories to which the user has explicit administrator access. * `contributor`: Repositories to which the user has explicit write access. By default, if not specified, no user role filter will be applied. ``` - kind: repository selector: query: 'true' userRole: owner ``` The `repoQuery` selector allows you to filter repositories based on a custom query. You can specify a custom query to include in the sync. ``` - kind: repository selector: query: 'true' repoQuery: 'language="python"' ``` ### Pull request configuration options[​](#pull-request-configuration-options "Direct link to Pull request configuration options") Pull request performance impact Non-open pull requests (such as `MERGED` or `DECLINED`) can accumulate to an extremely large volume over time, which may significantly increase the time required to complete a resync for the integration. To prevent performance degradation, if you intend to ingest non-open pull requests, you must add a filtering criteria such as `updated_on` to limit the number of items ingested. The example above demonstrates this by retrieving all `OPEN` pull requests, while limiting `MERGED` pull requests to those updated after a specific timestamp. The `pullRequestQuery` selector allows you to filter pull requests based on a custom query. You can specify a custom query per the official BitBucket filtering and sorting specifications to include in the sync. By default, if not specified, all open pull requests will be ingested. ``` - kind: pull-request selector: query: 'true' pullRequestQuery: 'state="OPEN" OR (state="MERGED" AND updated_on > "2025-12-10T14:00:00-07:00")' ``` After creating the blueprints and updating the mapping, you will see new entities in Port matching your repositories alongside their README.md file contents and pull requests. ## Repositories and monorepos[​](#repositories-and-monorepos "Direct link to Repositories and monorepos") The following example demonstrates how to ingest your Bitbucket repositories and their folders to Port. By following this example you can map your different repositories, packages and libraries from your monorepo into separate entities in Port: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "bitbucketRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md folders: # Specify the repositories and folders to include under this relative path. - path: integrations/* # Relative path to the folders within the repositories. repos: # List of repositories to include folders from. - name: port-repo-3 # the name of the repository to include folders from. branch: new # Optional; branch name to map the folders from, defaults to the repository's default branch. - path: integrations/**/* repos: - name: port-repo-3 port: entity: mappings: identifier: .folder.path title: .folder.path | split("/") | last blueprint: '"bitbucketRepository"' properties: url: >- .repo.links.html.href + "/src/" + .repo.mainbranch.name + "/" + .folder.path defaultBranch: .repo.mainbranch.name readme: .__includedFiles["README.md"] ``` ## Files[​](#files "Direct link to Files") The following example demonstrates how to ingest files from a specific repository. You will be able to map files and file contents from specific repositories as entities in Port: User-Scoped Token Required The `file` kind requires **user-scoped token** (or existing app password) for authentication. Workspace tokens do not support file ingestion. Since new app passwords cannot be created as of September 9, 2025, ensure you've configured a user-scoped token in your integration setup. **File blueprint (click to expand)** Create in Port ``` { "identifier": "file", "title": "File", "icon": "Bitbucket", "schema": { "properties": { "path": { "title": "Path", "type": "string", "icon": "DefaultProperty", "description": "The path of the file in the repository" }, "content": { "title": "Content", "type": "string", "icon": "DefaultProperty", "description": "The content of the file" }, "link": { "title": "Link", "type": "string", "format": "url", "icon": "Link", "description": "Link to the file in Bitbucket" }, "required": ["path"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "bitbucketRepository", "required": true, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` resources: # IMPORTANT: The 'file' kind requires user-scoped token (or existing app password) # Workspace tokens do not support file ingestion - kind: file selector: query: 'true' files: path: integrations/bitbucket-cloud/ #Absolute path to the file to sync from the repository; The more specific the path the better; filenames: #Required; List of files to sync from the repository in the given path; - README.md #Just the filename, no wildcards; - port.yaml #Just the filename, no wildcards; repo: #Optional; List of repositories to include files from. If not provided, all repositories will be included; - port-repo-3 skipParsing: true #Optional; If true, the file will not be parsed by Port; port: entity: mappings: identifier: .metadata.path blueprint: '"file"' properties: content: .content path: .metadata.path link: .repo.links.html.href + "/src/" + .branch + "/" + .metadata.path relations: repository: .repo.name ``` ## Supported resources[​](#supported-resources "Direct link to Supported resources") The above examples show specific use cases, but Port's Bitbucket integration supports other use cases as well. To adapt the examples shown above, use the Bitbucket API reference to learn about the available fields for the different supported objects: * [`projects`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-workspace-projects-get) * [`repository`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-get) * [`pull-request`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-pullrequests-pull-request-id-get) * [`folder`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get) * [`file`](https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get) When adding the ingestion of other resources, remember to add a entry to the `resources` array and change the value provided to the `kind` key accordingly. --- # GitOps Port's Bitbucket integration makes it possible to manage Port entities with a GitOps approach, making your code repositories into the source of truth for the various infrastructure assets you want to manage. Bitbucket Server (Self-Hosted) This documentation covers GitOps for **Bitbucket Cloud**. For information about GitOps with Bitbucket Server (Self-Hosted), please refer to the [Bitbucket Server GitOps documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/gitops/.md). ## 💡 Bitbucket GitOps common use cases[​](#-bitbucket-gitops-common-use-cases "Direct link to 💡 Bitbucket GitOps common use cases") * Use Bitbucket as the source-of-truth for your **microservices**, **packages**, **libraries** and other software catalog assets. * Allow developers to keep the catalog up-to-date, by making updates to files in their Git repositories. * Create a standardized way to document software catalog assets in your organization. ## Managing entities using GitOps[​](#managing-entities-using-gitops "Direct link to Managing entities using GitOps") To manage entities using GitOps, you will need to add a `port.yml` file to the **default branch** (usually `main`) of your repository. Note that the `port.yml` file is not the same as the [`port-app-config.yml` file](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/.md#port-app-configyml-file) used to configure the BitBucket integration, and does not replace it. The `port.yml` file can specify one or more Port entities that will be ingested to Port, and any change made to the `port.yml` file will also be reflected inside Port. This configuration turns your Bitbucket repositories to the source-of-truth for the software catalog. ### GitOps `port.yml` file[​](#gitops-portyml-file "Direct link to gitops-portyml-file") The `port.yml` file is how you specify your Port entities that are managed using GitOps and whose data is ingested from your Git repositories. Here are examples for valid `port.yml` files: * Single entity * Multiple entities ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ``` - identifier: myEntity1 title: My Entity1 blueprint: myFirstBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 - identifier: myEntity title: My Entity2 blueprint: mySecondBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ``` Since both of the valid `port.yml` formats follow the same structure, the following section will explain the format based on the single entity example. ### `port.yml` structure[​](#portyml-structure "Direct link to portyml-structure") Here is an example `port.yml` file: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` * The `identifier` key is used to specify the identifier of the entity that the app will create and keep up-to-date when changes occur: ``` identifier: myEntity title: My Entity ... ``` * The `title` key is used to specify the title of the entity: ``` identifier: myEntity title: My Entity ... ``` * The `blueprint` key is used to specify the identifier of the blueprint to create this entity from: ``` ... title: My Entity blueprint: myBlueprint ... ``` * The `properties` key is used to map the values to the different properties of the entity: ``` ... title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ... ``` * The `relations` key is used to map target entities to the different relations of the entity: * Single relation * Many relation ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity ``` ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ### Ingesting repository file contents[​](#ingesting-repository-file-contents "Direct link to Ingesting repository file contents") It is possible to use the contents of files in the repository as the value for entity properties using a simple reference. The following example will read the string contents of `~/module1/README.md` and upload it to `myStringProp` of the specified entity. Repository folder structure used for the example: ``` root | +- port.yml | +-+ module1 | | | +- README.md | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: myStringProp: file://module1/README.md ``` #### Using relative paths[​](#using-relative-paths "Direct link to Using relative paths") It is also possible to use paths relative to the location of the `port.yml` spec file. For example: `file://./` is used to reference a file in the same directory as the `port.yml` file. `file://../` is used to reference a file that is one directory above and so on. The following example reads `README.md` and `module1/requirements.txt` using paths relative to `port.yml` Repository folder structure used for the example: ``` root | +-+ meta | | | +-- port.yml | | | +-+ README.md | +-+ module1 | | | +- requirements.txt | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: readme: file://./README.md module1Requirements: file://../module1/requirements.txt ``` ## Examples[​](#examples "Direct link to Examples") Check out the [example repository](https://github.com/port-labs/github-app-setup-example) for a microservice blueprint and a matching `port.yml` file which specifies a microservice entity. ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Port.yml ingestion pattern[​](#portyml-ingestion-pattern "Direct link to Port.yml ingestion pattern") The `port.yml` ingestion pattern allows you to configure the Bitbucket integration to ingest `port.yml` files as part of the resync process. This approach is particularly useful when you want to maintain data integrity and ensure that your `port.yml` files are properly synchronized with Port. `port.yml` files operate in the GitOps methodology, meaning they are ingested into Port whenever a commit to the main branch of the repository is detected. Here's how to configure the integration to parse and ingest `port.yml` files: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: entity: mappings: identifier: .content.identifier title: .content.title blueprint: .content.blueprint properties: property_a: .content.properties.property_a property_b: .content.properties.property_b relations: relation_a: .content.relations.relation_a relation_b: .content.relations.relation_b ``` For multiple entities in a single `port.yml` file, use the `itemsToParse` key: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: itemsToParse: .content entity: mappings: identifier: .item.identifier title: .item.title blueprint: .item.blueprint properties: property_a: .item.properties.property_a property_b: .item.properties.property_b relations: relation_a: .item.relations.relation_a relation_b: .item.relations.relation_b ``` ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/advanced/.md) page for advanced use cases and configurations. --- # Bitbucket (self-hosted) ![BitBucket icon](/img/guides/icons/BitBucket.svg)![BitBucket icon](/img/guides/icons/BitBucket.svg) Loading version... 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. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-project/#api-api-latest-projects-get)[Repository](https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-project/#api-api-latest-projects-projectkey-repos-get)[Pull Request](https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-pull-requests/#api-api-latest-projects-projectkey-repos-repositoryslug-pull-requests-get)[User](https://developer.atlassian.com/server/bitbucket/rest/v906/api-group-permission-management/#api-api-latest-admin-users-get) ## Core support [Live eventsSee supported resources](#set-up-live-events) **Note:** While Bitbucket Server has been [deprecated and replaced by Bitbucket Data Center](https://www.atlassian.com/blog/announcements/journey-to-cloud), they expose the same set of APIs and this integration covers both. For Bitbucket Cloud, see the [Bitbucket Cloud integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/.md). ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Filtering data[​](#filtering-data "Direct link to 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 with `TEAM-`. * `.*-PROD$` — matches projects whose keys end with `-PROD`. * `^(PROJ|INFRA)$` — matches the exact keys `PROJ` or `INFRA`. Combining filters 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[​](#default-mapping-configuration "Direct link to 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[​](#mapping--selectors-per-resource "Direct link to 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 [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ProjectRepositoryPull RequestUser Default mappingYAML ``` - 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 ``` Sample payloadJSON ``` { "key": "PROJ", "id": 1, "name": "Project", "public": false, "type": "NORMAL", "links": { "self": [ { "href": "http://localhost:7990/projects/PROJ" } ] } } ``` Port entityJSON ``` { "blueprint": "bitbucketProject", "identifier": "PROJ", "createdAt": "2025-05-20T09:14:22.361Z", "updatedBy": "jqoQ34Azuy08BJFFUZAKyP3sXranvmgc", "createdBy": "jqoQ34Azuy08BJFFUZAKyP3sXranvmgc", "team": [], "title": "Project", "relations": {}, "properties": { "public": false, "link": "http://localhost:7990/projects/PROJ", "description": null, "type": "NORMAL" }, "updatedAt": "2025-05-22T20:46:27.616Z" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `projects` | `array` | List of project keys to filter. If empty, all projects will be synced | | `projectFilterRegex` | `string` | Optional regex pattern to filter project keys (e.g., '^PROJ-.*' to include only projects starting with 'PROJ-', or '.*-PROD$' to include only projects ending with '-PROD') | Default mappingYAML ``` - 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 ``` Sample payloadJSON ``` { "slug": "repostiroy-3", "id": 3, "name": "Repostiroy-3", "hierarchyId": "0a8dadb07bb606236d8c", "scmId": "git", "state": "AVAILABLE", "statusMessage": "Available", "forkable": true, "project": { "key": "PRO3", "id": 3, "name": "Project Three", "public": false, "type": "NORMAL", "links": { "self": [ { "href": "http://localhost:7990/projects/PRO3" } ] } }, "public": false, "archived": false, "links": { "clone": [ { "href": "ssh://git@localhost:7999/pro3/repostiroy-3.git", "name": "ssh" }, { "href": "http://localhost:7990/scm/pro3/repostiroy-3.git", "name": "http" } ], "self": [ { "href": "http://localhost:7990/projects/PRO3/repos/repostiroy-3/browse" } ] }, "__readme": "", "__latestCommit": { "id": "965068d1461f119139bb6be582bb22a555a195ba", "displayId": "965068d1461", "author": { "name": "[REDACTED]", "emailAddress": "admin@gmail.com", "active": true, "displayName": "Admin", "id": 3, "slug": "[REDACTED]", "type": "NORMAL", "links": { "self": [ { "href": "http://localhost:7990/users/[REDACTED]" } ] } }, "authorTimestamp": 1747744649000, "committer": { "name": "[REDACTED]", "emailAddress": "admin@gmail.com", "active": true, "displayName": "Admin", "id": 3, "slug": "[REDACTED]", "type": "NORMAL", "links": { "self": [ { "href": "http://localhost:7990/users/[REDACTED]" } ] } }, "committerTimestamp": 1747744649000, "message": "Pull request #1: readme.md edited online with Bitbucket\n\nMerge in PRO3/repostiroy-3 from main to master\n\n* commit '3e4df0573a0ba1845ebdfa919c907745497313aa':\n readme.md edited online with Bitbucket", "parents": [ { "id": "9534663d88977c0aa5c25249986eae851fd83a8d", "displayId": "9534663d889" }, { "id": "3e4df0573a0ba1845ebdfa919c907745497313aa", "displayId": "3e4df0573a0" } ] } } ``` Port entityJSON ``` { "blueprint": "bitbucketRepository", "identifier": "repostiroy-3", "createdAt": "2025-05-20T13:14:09.505Z", "updatedBy": "jqoQ34Azuy08BJFFUZAKyP3sXranvmgc", "createdBy": "jqoQ34Azuy08BJFFUZAKyP3sXranvmgc", "team": [], "title": "Repostiroy-3", "relations": { "project": "PRO3", "latestCommitAuthor": "admin@gmail.com" }, "properties": { "public": false, "documentation": "", "link": "http://localhost:7990/projects/PRO3/repos/repostiroy-3/browse", "forkable": true, "description": null, "state": "AVAILABLE", "readme": null, "swagger_url": null }, "updatedAt": "2025-05-20T13:14:09.505Z" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `projects` | `array` | List of project keys to filter. If empty, all projects will be synced | | `projectFilterRegex` | `string` | Optional regex pattern to filter project keys (e.g., '^PROJ-.*' to include only projects starting with 'PROJ-', or '.*-PROD$' to include only projects ending with '-PROD') | Default mappingYAML ``` - 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]" ``` Sample payloadJSON ``` { "id": 1, "version": 1, "title": "readme.md edited online with Bitbucket", "state": "OPEN", "open": true, "closed": false, "draft": false, "createdDate": 1747730324792, "updatedDate": 1747730324792, "fromRef": { "id": "refs/heads/main", "displayId": "main", "latestCommit": "3e4df0573a0ba1845ebdfa919c907745497313aa", "type": "BRANCH", "repository": { "slug": "repostiroy-3", "id": 3, "name": "Repostiroy 3", "hierarchyId": "0a8dadb07bb606236d8c", "scmId": "git", "state": "AVAILABLE", "statusMessage": "Available", "forkable": true, "project": { "key": "PRO3", "id": 3, "name": "Project Three", "public": false, "type": "NORMAL", "links": { "self": [ { "href": "http://localhost:7990/projects/PRO3" } ] } }, "public": false, "archived": false, "links": { "clone": [ { "href": "ssh://git@localhost:7999/pro3/repostiroy-3.git", "name": "ssh" }, { "href": "http://localhost:7990/scm/pro3/repostiroy-3.git", "name": "http" } ], "self": [ { "href": "http://localhost:7990/projects/PRO3/repos/repostiroy-3/browse" } ] } } }, "toRef": { "id": "refs/heads/master", "displayId": "master", "latestCommit": "9534663d88977c0aa5c25249986eae851fd83a8d", "type": "BRANCH", "repository": { "slug": "repostiroy-3", "id": 3, "name": "Repostiroy 3", "hierarchyId": "0a8dadb07bb606236d8c", "scmId": "git", "state": "AVAILABLE", "statusMessage": "Available", "forkable": true, "project": { "key": "PRO3", "id": 3, "name": "Project Three", "public": false, "type": "NORMAL", "links": { "self": [ { "href": "http://localhost:7990/projects/PRO3" } ] } }, "public": false, "archived": false, "links": { "clone": [ { "href": "ssh://git@localhost:7999/pro3/repostiroy-3.git", "name": "ssh" }, { "href": "http://localhost:7990/scm/pro3/repostiroy-3.git", "name": "http" } ], "self": [ { "href": "http://localhost:7990/projects/PRO3/repos/repostiroy-3/browse" } ] } } }, "locked": false, "author": { "user": { "name": "[REDACTED]", "emailAddress": "admin@gmail.com", "active": true, "displayName": "Admin", "id": 3, "slug": "[REDACTED]", "type": "NORMAL", "links": { "self": [ { "href": "http://localhost:7990/users/[REDACTED]" } ] } }, "role": "AUTHOR", "approved": false, "status": "UNAPPROVED" }, "reviewers": [], "participants": [], "properties": { "mergeResult": { "outcome": "CLEAN", "current": false }, "resolvedTaskCount": 0, "commentCount": 0, "openTaskCount": 0 }, "links": { "self": [ { "href": "http://localhost:7990/projects/PRO3/repos/repostiroy-3/pull-requests/1" } ] } } ``` Port entityJSON ``` { "blueprint": "bitbucketPullRequest", "identifier": "1", "createdAt": "2025-05-20T09:22:29.565Z", "updatedBy": "jqoQ34Azuy08BJFFUZAKyP3sXranvmgc", "createdBy": "jqoQ34Azuy08BJFFUZAKyP3sXranvmgc", "team": [], "title": "readme.md edited online with Bitbucket", "relations": { "repository": "repostiroy-3", "participants": [] }, "properties": { "updated_on": "2025-05-20T12:37:29Z", "owner": "admin@gmail.com", "created_on": "2025-05-20T08:38:44Z", "mergedAt": "2025-05-20T12:37:29Z", "link": "http://localhost:7990/projects/PRO3/repos/repostiroy-3/pull-requests/1", "destination": "master", "description": null, "state": "MERGED", "source": "main", "reviewers": [], "merge_commit": "3e4df0573a0ba1845ebdfa919c907745497313aa" }, "updatedAt": "2025-05-20T12:37:34.111Z" } ``` Default mappingYAML ``` - kind: user selector: query: "true" port: entity: mappings: identifier: .emailAddress title: .displayName blueprint: '"bitbucketUser"' properties: username: .name url: .links.self[0].href portUser: .emailAddress ``` Sample payloadJSON ``` { "name": "[REDACTED]", "emailAddress": "admin@gmail.com", "active": true, "displayName": "Admin", "id": 3, "slug": "[REDACTED]", "type": "NORMAL", "links": { "self": [ { "href": "http://localhost:7990/users/[REDACTED]" } ] } } ``` Port entityJSON ``` { "blueprint": "bitbucketUser", "identifier": "admin@gmail.com", "createdAt": "2025-05-20T09:10:04.195Z", "updatedBy": "jqoQ34Azuy08BJFFUZAKyP3sXranvmgc", "createdBy": "jqoQ34Azuy08BJFFUZAKyP3sXranvmgc", "team": [], "title": "Admin", "relations": {}, "properties": { "portUser": "admin@gmail.com", "url": "http://localhost:7990/users/admin", "username": "admin" }, "updatedAt": "2025-05-20T09:10:04.195Z" } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 as `liveEvents.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. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## GitOps[​](#gitops "Direct link to 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](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/gitops/.md) installation method. --- # GitOps Port's Bitbucket (Self-Hosted) makes it possible to manage Port entities with a GitOps approach, making your code repositories into the source of truth for the various infrastructure assets you want to manage. ## 💡 Bitbucket GitOps common use cases[​](#-bitbucket-gitops-common-use-cases "Direct link to 💡 Bitbucket GitOps common use cases") * Use Bitbucket (Self-Hosted) as the source-of-truth for your **microservices**, **packages**, **libraries** and other software catalog assets. * Allow developers to keep the catalog up-to-date, by making updates to files in their Git repositories. * Create a standardized way to document software catalog assets in your organization. ## Managing entities using GitOps[​](#managing-entities-using-gitops "Direct link to Managing entities using GitOps") To manage entities using GitOps, you will need to add a `port.yaml` file to the **root directory** of your repository in the **default branch** (usually `main`). The `port.yaml` file must be placed in the root directory of your repository. Repository folder structure should look like this example: ``` root | +- port.yml | +-+ module1 | | | +- README.md | | | +-+ src ... ``` The `port.yaml` file can specify one or more Port entities that will be ingested to Port, and any change made to the `port.yaml` file will also be reflected inside Port. This configuration turns your Bitbucket (Self-Hosted) repositories to the source-of-truth for the software catalog. ### GitOps port.yaml file[​](#gitops-portyaml-file "Direct link to GitOps port.yaml file") The `port.yaml` file defines your Port entities, which are managed via GitOps and have their data ingested from your Git repositories Here are examples for valid `port.yaml` files: * Single entity * Multiple entities ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ``` - identifier: myEntity1 title: My Entity1 blueprint: myFirstBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 - identifier: myEntity title: My Entity2 blueprint: mySecondBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ``` Since both of the valid `port.yaml` formats follow the same structure, the following section will explain the format based on the single entity example. ### port.yaml structure[​](#portyaml-structure "Direct link to port.yaml structure") The `port.yaml` file has the following structure: * `identifier` (required): A unique identifier for the entity. * `title` (required): The display name of the entity. * `blueprint` (required): The blueprint identifier that this entity is an instance of. * `properties` (optional): A map of property values for the entity. * `relations` (optional): A map of relation values for the entity. ## Implementation[​](#implementation "Direct link to Implementation") To implement GitOps with Bitbucket (Self-Hosted), you can use the provided Python script that will scan your repositories for `port.yaml` files and create or update entities in Port accordingly. ### Prerequisites To use the GitOps functionality, you need to provide the following environment variables: * `PORT_CLIENT_ID` - Your Port client id * `PORT_CLIENT_SECRET` - Your Port client secret * `BITBUCKET_HOST` - Bitbucket (Self-Hosted) host such as `http://localhost:7990` * `BITBUCKET_USERNAME` - Bitbucket username to use when accessing the Bitbucket resources * `BITBUCKET_PASSWORD` - Bitbucket account password * `BITBUCKET_PROJECTS_FILTER` - An optional comma separated list of Bitbucket projects to filter. If not provided, all projects will be fetched. * `PORT_API_URL` - An optional variable that defaults to the EU Port API `https://api.port.io/v1`. For US organizations use `https://api.us.port.io/v1` instead. ### Python script Here's a Python script that you can use to implement GitOps with Bitbucket (Self-Hosted): **Python script (click to expand)** Latest Version You can pull the latest version of this code by cloning this [repository](https://github.com/port-labs/bitbucket-workspace-data.git) ``` import time from datetime import datetime, timedelta import asyncio from typing import Any, Optional, Dict import httpx import yaml from decouple import config from loguru import logger from httpx import BasicAuth from pydantic import BaseModel, ValidationError # These are the credentials passed by the variables of your pipeline to your tasks and into your env PORT_CLIENT_ID = config("PORT_CLIENT_ID") PORT_CLIENT_SECRET = config("PORT_CLIENT_SECRET") BITBUCKET_USERNAME = config("BITBUCKET_USERNAME") BITBUCKET_PASSWORD = config("BITBUCKET_PASSWORD") BITBUCKET_API_URL = config("BITBUCKET_HOST") BITBUCKET_PROJECTS_FILTER = config( "BITBUCKET_PROJECTS_FILTER", cast=lambda v: v.split(",") if v else None, default=[] ) PORT_API_URL = config("PORT_API_URL", default="https://api.port.io/v1") # According to https://support.atlassian.com/bitbucket-cloud/docs/api-request-limits/ RATE_LIMIT = 1000 # Maximum number of requests allowed per hour RATE_PERIOD = 3600 # Rate limit reset period in seconds (1 hour) request_count = 0 rate_limit_start = time.time() port_access_token, token_expiry_time = None, datetime.now() port_headers = {} bitbucket_auth = BasicAuth(username=BITBUCKET_USERNAME, password=BITBUCKET_PASSWORD) client = httpx.AsyncClient(timeout=httpx.Timeout(60)) async def get_access_token(): credentials = {"clientId": PORT_CLIENT_ID, "clientSecret": PORT_CLIENT_SECRET} token_response = await client.post( f"{PORT_API_URL}/auth/access_token", json=credentials ) response_data = token_response.json() access_token = response_data["accessToken"] expires_in = response_data["expiresIn"] token_expiry_time = datetime.now() + timedelta(seconds=expires_in) return access_token, token_expiry_time async def refresh_access_token(): global port_access_token, token_expiry_time, port_headers logger.info("Refreshing access token...") port_access_token, token_expiry_time = await get_access_token() port_headers = {"Authorization": f"Bearer {port_access_token}"} logger.info(f"New token received. Expiry time: {token_expiry_time}") async def refresh_token_if_expired(): if datetime.now() >= token_expiry_time: await refresh_access_token() async def refresh_token_and_retry(method: str, url: str, **kwargs): await refresh_access_token() response = await client.request(method, url, headers=port_headers, **kwargs) return response async def send_port_request(method: str, endpoint: str, payload: Optional[dict] = None): global port_access_token, token_expiry_time, port_headers await refresh_token_if_expired() url = f"{PORT_API_URL}/{endpoint}" try: response = await client.request(method, url, headers=port_headers, json=payload) response.raise_for_status() return response except httpx.HTTPStatusError as e: if e.response.status_code == 401: # Unauthorized, refresh token and retry logger.info("Received 401 Unauthorized. Refreshing token and retrying...") try: response = await refresh_token_and_retry(method, url, json=payload) response.raise_for_status() return response except httpx.HTTPStatusError as e: logger.error( f"Error after retrying: {e.response.status_code}, {e.response.text}" ) return {"status_code": e.response.status_code, "response": e.response} else: logger.error( f"HTTP error occurred: {e.response.status_code}, {e.response.text}" ) return {"status_code": e.response.status_code, "response": e.response} except httpx.HTTPError as e: logger.error(f"HTTP error occurred: {e}") return {"status_code": None, "error": e} async def add_entity_to_port(blueprint_id, entity_object): response = await send_port_request( method="POST", endpoint=f"blueprints/{blueprint_id}/entities?upsert=true&merge=true", payload=entity_object, ) if not isinstance(response, dict): logger.info(response.json()) async def get_paginated_resource( path: str, params: dict[str, Any] = None, page_size: int = 25, full_response: bool = False, ): global request_count, rate_limit_start # Check if we've exceeded the rate limit, and if so, wait until the reset period is over if request_count >= RATE_LIMIT: elapsed_time = time.time() - rate_limit_start if elapsed_time < RATE_PERIOD: sleep_time = RATE_PERIOD - elapsed_time await asyncio.sleep(sleep_time) # Reset the rate limiting variables request_count = 0 rate_limit_start = time.time() url = f"{BITBUCKET_API_URL}/rest/api/1.0/{path}" params = params or {} params["limit"] = page_size next_page_start = None while True: try: if next_page_start: params["start"] = next_page_start response = await client.get(url=url, auth=bitbucket_auth, params=params) response.raise_for_status() page_json = response.json() request_count += 1 logger.debug( f"Requested data for {path}, with params: {params} and response code: {response.status_code}" ) if full_response: yield page_json else: batch_data = page_json["values"] yield batch_data next_page_start = page_json.get("nextPageStart") if not next_page_start: break except httpx.HTTPStatusError as e: if e.response.status_code == 404: logger.info( f"Could not find the requested resources {path}. Terminating gracefully..." ) return logger.error( f"HTTP error with code {e.response.status_code}, content: {e.response.text}" ) except httpx.HTTPError as e: logger.error(f"HTTP occurred while fetching Bitbucket data: {e}") logger.info(f"Successfully fetched paginated data for {path}") async def get_single_project(project_key: str): response = await client.get( f"{BITBUCKET_API_URL}/rest/api/1.0/projects/{project_key}", auth=bitbucket_auth ) response.raise_for_status() return response.json() def parse_repository_file_response(file_response: dict[str, Any]) -> str: lines = file_response.get("lines", []) logger.info(f"Received port.yaml file with {len(lines)} entries") content = "" for line in lines: content += line.get("text", "") + "\n" return content async def get_repositories(project: dict[str, Any]): repositories_path = f"projects/{project['key']}/repos" async for repositories_batch in get_paginated_resource(path=repositories_path): logger.info( f"received repositories batch with size {len(repositories_batch)} from project: {project['key']}" ) await asyncio.gather( *( create_or_update_entity_from_yaml( project_key=project["key"], repo_slug=repo["slug"] ) for repo in repositories_batch ) ) async def read_port_yaml_from_bitbucket(project_key, repo_slug): url = f"projects/{project_key}/repos/{repo_slug}/browse/port.yaml" port_yaml_file = "" async for port_file_batch in get_paginated_resource( path=url, page_size=500, full_response=True ): file_content = parse_repository_file_response(port_file_batch) port_yaml_file += file_content return yaml.safe_load(port_yaml_file) async def create_or_update_entity_from_yaml(project_key, repo_slug): try: entity_data = await read_port_yaml_from_bitbucket(project_key, repo_slug) if entity_data: logger.info(f"Creating entity from port.yaml: {entity_data}") if isinstance(entity_data, dict): validated_entity = validate_port_yaml(entity_data) if validated_entity: await add_entity_to_port( blueprint_id=entity_data.get("blueprint"), entity_object=entity_data ) elif isinstance(entity_data, list): for entity in entity_data: validated_entity = validate_port_yaml(entity) if validated_entity: await add_entity_to_port( blueprint_id=entity.get("blueprint"), entity_object=entity ) else: logger.error(f"Invalid entity schema: {entity}") else: logger.error(f"Invalid entity port.yaml schema : {entity_data} with type {type(entity_data)}") except Exception as e: logger.error(f"Error reading port.yaml file: {str(e)}") return class PortEntity(BaseModel): identifier: str title: str blueprint: str properties: Dict[str, Any] relations: Dict[str, Any] def validate_port_yaml(data: dict): try: data["properties"] = data.get("properties") or {} data["relations"] = data.get("relations") or {} validated_entity = PortEntity(**data) return validated_entity.model_dump() except ValidationError as e: logger.error(f"Validation error for entity: {e.json()}") return None except Exception as e: logger.error(f"Error validating entity: {e}") return None async def main(): logger.info("Starting Bitbucket data extraction") if BITBUCKET_PROJECTS_FILTER: async def filtered_projects_generator(): yield [await get_single_project(key) for key in BITBUCKET_PROJECTS_FILTER] projects = filtered_projects_generator() else: projects = get_paginated_resource(path="projects") async for projects_batch in projects: logger.info(f"received projects batch with size {len(projects_batch)}") for project in projects_batch: await get_repositories(project=project) logger.info("Bitbucket gitops completed") await client.aclose() if __name__ == "__main__": asyncio.run(main()) ``` ### Running the script You can run this script periodically (e.g., using a cron job or CI/CD pipeline) to scan your Bitbucket (Self-Hosted) repositories for `port.yaml` files and create or update entities in Port accordingly. For example, you can create a GitHub Actions workflow that runs this script on a schedule: Host accessibility If you're running this script using GitHub Actions or any other external CI/CD service, your Bitbucket (Self-Hosted) host must be accessible over the internet. If your Bitbucket (Self-Hosted) is deployed in a private network or behind a firewall, you'll need to ensure it's properly exposed or consider running the script from a machine within the same network that has access to both the Bitbucket (Self-Hosted) and the internet. ``` name: Bitbucket (Self-Hosted) GitOps on: schedule: - cron: '0 */6 * * *' # Run every 6 hours workflow_dispatch: # Allow manual triggering jobs: sync: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v7 with: python-version: '3.9' - name: Install dependencies run: | python -m pip install --upgrade pip pip install httpx pydantic python-decouple loguru pyyaml - name: Run GitOps script env: PORT_CLIENT_ID: ${{ secrets.PORT_CLIENT_ID }} PORT_CLIENT_SECRET: ${{ secrets.PORT_CLIENT_SECRET }} BITBUCKET_USERNAME: ${{ secrets.BITBUCKET_USERNAME }} BITBUCKET_PASSWORD: ${{ secrets.BITBUCKET_PASSWORD }} BITBUCKET_HOST: ${{ secrets.BITBUCKET_HOST }} run: python gitops.py ``` ## Examples[​](#examples "Direct link to Examples") Here's an example of a `port.yaml` file for a microservice: ``` identifier: my-service title: My Service blueprint: microservice properties: description: A service that does something awesome language: Python version: 1.0.0 tier: Backend lifecycle: Production owner: Team A documentation: https://example.com/docs relations: repository: my-service-repo team: team-a ``` This will create or update a microservice entity in Port with the specified properties and relations. ## Limitations[​](#limitations "Direct link to Limitations") * The GitOps script has a limitation regarding entity deletion - when an entity is removed from the `port.yaml` file, it will not be automatically deleted from your Port catalog. The script only handles creation and updates of entities that are explicitly defined in the `port.yaml` files. To remove entities that are no longer needed in your catalog, you will need to delete them manually through Port's UI or API. * The Bitbucket (Self-Hosted) Ocean integration by itself does not support GitOps yet. This capability is planned for a future release and is WIP. GitOps support is provided in this guide instead. ## Conclusion[​](#conclusion "Direct link to Conclusion") Using GitOps with Bitbucket (Self-Hosted) allows you to manage your Port entities directly from your code repositories, making it easier to keep your software catalog up-to-date and accurate. By adding a `port.yaml` file to your repositories, you can define the entities that should be created or updated in Port, and the provided Python script will handle the synchronization for you. --- # GitHub (Ocean) ![GitHub icon](/img/guides/icons/GitHub.svg)![GitHub icon](/img/guides/icons/dark/GitHub.svg) Loading version... The GitHub Ocean integration allows you to model GitHub resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Organization](https://docs.github.com/en/rest/orgs?apiVersion=2022-11-28#list-organizations-for-the-authenticated-user)[Repository](https://docs.github.com/en/rest/repos#get-a-repository)[Pull Request](https://docs.github.com/en/rest/pulls#get-a-pull-request)[Issue](https://docs.github.com/en/rest/issues#get-an-issue)[File](https://docs.github.com/en/rest/repos/contents#get-repository-content)[Folder](https://docs.github.com/en/rest/git/trees#get-a-tree)[Skill](https://agentskills.io/specification)[Plugin](https://docs.github.com/en/rest/git/trees#get-a-tree)[MCP](https://cursor.com/docs/reference/plugins#mcp-servers)[User](https://docs.github.com/en/rest/users#get-a-user)[Team](https://docs.github.com/en/rest/teams#get-a-team-by-name)[Workflow](https://docs.github.com/en/rest/actions/workflows#get-a-workflow)[Workflow Run](https://docs.github.com/en/rest/actions/workflow-runs#get-a-workflow-run)[Branch](https://docs.github.com/en/rest/branches#get-a-branch)[Environment](https://docs.github.com/en/rest/deployments/environments#get-an-environment)[Deployment](https://docs.github.com/en/rest/deployments#get-a-deployment)[Deployment Status](https://docs.github.com/en/rest/deployments/statuses#get-a-deployment-status)[Release](https://docs.github.com/en/rest/releases#list-releases)[Tag](https://docs.github.com/en/rest/repos#list-repository-tags)[Dependabot Alert](https://docs.github.com/en/rest/dependabot/alerts#list-dependabot-alerts-for-a-repository)[Code Scanning Alert](https://docs.github.com/en/rest/code-scanning#list-code-scanning-alerts-for-a-repository)[Secret Scanning Alert](https://docs.github.com/en/rest/secret-scanning#list-secret-scanning-alerts-for-a-repository)[Collaborator](https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#list-repository-collaborators)[Package](https://docs.github.com/en/rest/packages/packages?apiVersion=2022-11-28#get-a-package-for-an-organization) ## Core support [Incremental syncClosed betaSee supported resources](#incremental-sync)[Live eventsSee supported resources](#set-up-live-events) Migrating from the old GitHub app? If you are migrating from the old GitHub app integration, **disable the "Create default resources" toggle** in the **Advanced Configuration** section before clicking **Connect**. For a self-hosted GitHub Ocean installation, set `createPortResourcesOrigin: Empty` in your values file. This registers only the integration without creating default blueprints or mappings. Leaving this toggle enabled will create new default blueprints and mappings that may conflict with your existing data. See the [migration guide](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/migration/migration-steps/.md) for detailed instructions. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A GitHub account with permissions to create access tokens. * Your Port user role is set to `Admin`. ## Setup[​](#setup "Direct link to Setup") Choose your preferred authentication and installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1GitHub app2Hosted by Port3Install GitHub app (recommended) * ✧Create a GitHub app or let Port manage one for you * ✧Full control over app permissions and configuration * ✧Supports multi-organization discovery and larger rate limits Personal access token (PAT) * ✧Supports fine-grained or classic PAT * ✧Multi-organization support with classic PAT Hosted by Port ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party API into Port.
The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to transform data. ### Managing your configuration[​](#managing-your-configuration "Direct link to Managing your configuration") You can manage your GitHub Ocean configuration in two ways: * **Port UI**: Go to the [connectors](https://app.port.io/settings/data-sources) page, click on the installed GitHub Ocean integration, and modify the YAML configuration directly. This applies the configuration globally to all organizations and repositories the integration can access. * **GitHub file**: Set `repoManagedMapping: true` in the integration configuration YAML to load the mapping from a `port-app-config.yml` file in your GitHub organization. Create this file in the `.github-private` repository on the default branch for it to take effect as an org-level mapping. GitHub file configuration limitations * Only org-level configuration is supported — repository-level `port-app-config.yml` files are not yet supported for GitHub Ocean. * Multi-organization setups are not supported when using GitHub file configuration. ### Repository type[​](#repository-type "Direct link to Repository type") The `repositoryType` parameter filters which repositories are ingested. It corresponds to the `type` parameter in GitHub's [List organization repositories](https://docs.github.com/en/rest/repos?apiVersion=2022-11-28#list-organization-repositories) API. **Possible values (click to expand)** * `all` (default): All repositories accessible to the provided token. * `public`: Public repositories only. * `private`: Private repositories only. * `forks`: Forked repositories only. * `sources`: Non-forked repositories only. ### Multi-organization support[​](#multi-organization-support "Direct link to Multi-organization support") The GitHub integration supports syncing data from multiple GitHub organizations with a classic PAT (v3.0.0-beta+) or GitHub App authentication (v6.5.0+). * **GitHub App setup**: The integration discovers and syncs every organization where the GitHub App is installed. Set `githubOrganization` only when you want to limit the sync to one organization. * **Enterprise-owned apps**: Create or transfer the GitHub App to your enterprise so it is available to install in all of its organizations. You must still install the app in each organization you want to sync. * **Classic PAT setup**: Set `organizations: ["org1", "org2", "org3"]` in your mapping to choose organizations. If `organizations` is not specified, the integration syncs all organizations the classic PAT can access. **Multi-organization mapping example (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true organizations: - org1 - org2 # ... rest of your mapping (repositoryType, resources, etc.) ... ``` **Precedence:** If `githubOrganization` is set in the environment variables or config and `organizations` are also listed in the Port mapping, the integration prioritizes single-organization behavior and syncs only the `githubOrganization`. **Performance considerations:** Syncing multiple organizations increases the number of API calls to GitHub and may slow down the integration. The more organizations you sync, the longer the resync time and the higher the API rate limit consumption. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the complete default mapping for the GitHub Ocean integration. **Complete default mapping (click to expand)** ``` repositoryType: "all" deleteDependentEntities: true createMissingRelatedEntities: true resources: - kind: organization selector: query: "true" port: entity: mappings: identifier: .login title: .login blueprint: '"githubOrganization"' properties: login: .login id: .id nodeId: .node_id url: .url reposUrl: .repos_url eventsUrl: .events_url hooksUrl: .hooks_url issuesUrl: .issues_url membersUrl: .members_url publicMembersUrl: .public_members_url avatarUrl: .avatar_url description: if .description then .description else "" end - kind: repository selector: query: "true" includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: description: if .description then .description else "" end visibility: if .private then "private" else "public" end defaultBranch: .default_branch readme: .__includedFiles["README.md"] url: .html_url language: if .language then .language else "" end relations: organization: .owner.login - kind: pull-request selector: query: "true" states: ["open"] port: entity: mappings: identifier: ".head.repo.name + (.id|tostring)" title: ".title" blueprint: '"githubPullRequest"' properties: creator: ".user.login" assignees: "[.assignees[].login]" reviewers: "[.requested_reviewers[].login]" status: ".state" closedAt: ".closed_at" updatedAt: ".updated_at" mergedAt: ".merged_at" createdAt: ".created_at" prNumber: ".id" link: ".html_url" leadTimeHours: >- (.created_at as $createdAt | .merged_at as $mergedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($mergedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $mergedTimestamp | if $mergedTimestamp == null then null else (((($mergedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) end) relations: repository: .__repository ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") For blueprint and mapping examples per resource type, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/examples/.md). OrganizationRepositoryPull RequestIssueFileFolderUserTeamWorkflowWorkflow RunBranchEnvironmentDeploymentDeployment StatusReleaseTagDependabot AlertCode Scanning AlertSecret Scanning AlertPackage BlueprintJSON Create in Port ``` { "identifier": "githubOrganization", "description": "This blueprint represents a service in our software catalog", "title": "Organization", "icon": "Github", "schema": { "properties": { "login": { "type": "string", "title": "Organization Login", "description": "The GitHub organization login name" }, "id": { "type": "number", "title": "Organization ID", "description": "GitHub organization ID" }, "nodeId": { "type": "string", "title": "Node ID", "description": "GitHub GraphQL node ID" }, "url": { "type": "string", "title": "API URL", "description": "GitHub API URL for the organization" }, "reposUrl": { "type": "string", "title": "Repositories URL", "description": "URL to organization's repositories" }, "eventsUrl": { "type": "string", "title": "Events URL", "description": "URL to organization's events" }, "hooksUrl": { "type": "string", "title": "Webhooks URL", "description": "URL to organization's webhooks" }, "issuesUrl": { "type": "string", "title": "Issues URL", "description": "URL to organization's issues" }, "membersUrl": { "type": "string", "title": "Members URL", "description": "URL to organization's members" }, "publicMembersUrl": { "type": "string", "title": "Public Members URL", "description": "URL to organization's public members" }, "avatarUrl": { "type": "string", "title": "Avatar URL", "description": "Organization avatar image URL" }, "description": { "type": "string", "title": "Description", "description": "Organization description" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` Default mappingYAML ``` - kind: organization selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: .login title: .name blueprint: '"githubOrganization"' properties: url: .html_url description: .description repositories: .public_repos createdAt: .created_at ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: repository selector: query: "true" includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: description: if .description then .description else "" end visibility: if .private then "private" else "public" end defaultBranch: .default_branch readme: .__includedFiles["README.md"] url: .html_url language: if .language then .language else "" end relations: organization: .owner.login ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `includedFiles` | `array` | File paths to fetch and attach to the raw data under `__includedFiles`. E.g. \["README.md", "CODEOWNERS"] | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `include` | `array` | Fetch additional data related to the repository. The accepted values are: [teams](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/examples#:~:text=teams%20with%20access%20to%20the%20repository), [collaborators](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/examples#:~:text=collaborators%20of%20the%20repository), [sbom](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/examples#:~:text=%3A%20Ingests%20the-,Software%20Bill%20of%20Materials%20\(SBOM\),-for%20the%20repository) | | `includedRelations` | `object` | Fetch additional data related to the repository. The accepted values are: [teams, collaborators, sbom, custom properties and pages](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/examples/#repositories-with-multiple-relationships) | | `updatedSince` | `string` | Only include repositories updated after this date (ISO 8601). Ignored during incremental sync. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | `includedRelations` parameters | Parameter | Type | Description | | ------------------ | --------- | --------------------------------------------------------------------------------- | | `collaborators` | `object` | Fetch collaborators of the repository. | | `teams` | `boolean` | Include teams with access to the repository. Default: `false`. | | `sbom` | `boolean` | Include SBOM for the repository. Default: `false`. | | `customProperties` | `boolean` | Include organization custom property values for the repository. Default: `false`. | | `pages` | `boolean` | Include GitHub Pages configuration for the repository. Default: `false`. | BlueprintJSON Create in Port ``` { "identifier": "githubPullRequest", "title": "Pull Request", "icon": "Github", "schema": { "properties": { "creator": { "title": "Creator", "type": "string" }, "assignees": { "title": "Assignees", "type": "array" }, "reviewers": { "title": "Reviewers", "type": "array" }, "status": { "title": "Status", "type": "string", "enum": ["merged", "open", "closed"], "enumColors": { "merged": "purple", "open": "green", "closed": "red" } }, "closedAt": { "title": "Closed At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "mergedAt": { "title": "Merged At", "type": "string", "format": "date-time" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "link": { "format": "url", "type": "string" }, "leadTimeHours": { "title": "Lead Time in hours", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "days_old": { "title": "Days Old", "icon": "DefaultProperty", "calculation": "(now / 86400) - (.properties.createdAt | capture(\"(?\\\\d{4}-\\\\d{2}-\\\\d{2})\") | .date | strptime(\"%Y-%m-%d\") | mktime / 86400) | floor", "type": "number" } }, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: pull-request selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. states: ["open"] # List of Pull Request states to include: use ["open"], ["closed"], or ["open","closed"]. 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 | gsub("\\["; "-") | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")) assignees: '[.assignees[].login | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")]' reviewers: '[.requested_reviewers[].login | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")]' status: ".state" # merged, closed, opened closedAt: ".closed_at" updatedAt: ".updated_at" mergedAt: ".merged_at" createdAt: ".created_at" prNumber: ".number" link: ".url" leadTimeHours: >- (.created_at as $createdAt | .merged_at as $mergedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($mergedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $mergedTimestamp | if $mergedTimestamp == null then null else (((($mergedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) end) relations: repository: .__repository ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ----------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `states` | `array` | Filter pull requests by states (e.g. \['open']). Default: `["open"]`. | | `maxResults` | `integer` | Max number of merged pull requests. Defaults to 100 for the days lookback; with closedSinceDate set, leave empty to fetch all closed PRs back to the cutoff. Large numbers may cause rate limits. Merged PRs are only retrieved when 'closed' is selected in the state selector. | | `since` | `integer` | Numbers of days back for closed pull requests. Ignored during incremental sync. Default: `60`. | | `closedSinceDate` | `string` | Only ingest pull requests closed on or after this absolute date (ISO-8601, e.g. 2025-01-01 or 2025-01-01T00:00:00Z). Filters by close date and overrides the 'since' days lookback when set. Ignored during incremental sync. | | `api` | `string` | API to use for fetching pull requests (REST or GraphQL). Default: `"rest"`. | | `enrichWithFirstCommit` | `boolean` | When the api selector is set to graphql and this option is enabled, each pull request is enriched with the first commit on the branch (OID and committed timestamp in UTC). Use this to measure lead time from the initial commit through review and merge.This option will be ignored if the api selector is set to rest. Default: `false`. | | `excludeGraphqlFields` | `array` | When the api selector is set to graphql and this option is enabled, fields specified in this list will be omitted from the query. This is useful as a workaround for GitHub GraphQL instability or to reduce rate limit cost. This option will be ignored if the api selector is set to rest. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubIssue", "title": "Issue", "icon": "Github", "schema": { "properties": { "creator": { "title": "Creator", "type": "string" }, "assignees": { "title": "Assignees", "type": "array" }, "labels": { "title": "Labels", "type": "array" }, "status": { "title": "Status", "type": "string", "enum": ["open", "closed"], "enumColors": { "open": "green", "closed": "purple" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "closedAt": { "title": "Closed At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "description": { "title": "Description", "type": "string", "format": "markdown" }, "issueNumber": { "title": "Issue Number", "type": "number" }, "link": { "title": "Link", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "target": "githubRepository", "required": true, "many": false } } } ``` Default mappingYAML ``` - kind: issue selector: query: ".pull_request == null" # JQ boolean query. If evaluated to false - skip syncing the object. state: "closed" 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" ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `state` | `string` | Filter by issue state (open, closed, or all). Default: `"open"`. | | `labels` | `array` | Filter issues by labels; issues must have ALL specified labels (e.g. \['bug', 'enhancement']). | | `updatedSince` | `string` | Only include issues updated after this date (ISO 8601). Ignored during incremental sync. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON ``` { "identifier": "files", "title": "Files", "icon": "Github", "schema": { "properties": { "name": { "type": "string", "title": "Name" }, "path": { "type": "string", "title": "Path" } }, "required": [ "name", "path" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "repository", "target": "githubRepository", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: file selector: query: 'true' files: - path: 'README.md' organization: my-org # Optional if githubOrganization is set (required if not set) repos: # Optional: list repositories to include (omit to scan all repos) - name: test-repo branch: main port: entity: mappings: identifier: .name title: .name blueprint: '"files"' properties: name: .name path: .path relations: repository: .repository.name ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `includedFiles` | `array` | List of file paths to fetch and attach to the file entity. This selector will add the content of the file to the API response under the `__includedFiles` field. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `files` | `array` | Array of files to retrieve. Each cell can include: - **Path** - files path, supports glob pattern. Example: "\*\*/package.json"
- **Organization** - GitHub org to scan
- **Repos** - array of repositories used to fetch files from. Each repo includes name and branch. For more information, see [Our docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/examples#files-and-file-contents). | Default mappingYAML ``` - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: apps/* # Relative path to the folders within the repositories. organization: my-org # Optional if githubOrganization is set (required if not set) includedFiles: # These paths are resolved relative to each matched folder (e.g. apps/service-a/README.md) - README.md repos: # List of repositories to include folders from. - name: backend-service branch: main - name: frontend-service # if you don't specify a branch, we'll use the repository's default branch port: entity: mappings: identifier: .folder.path | split("/") | last title: .folder.path | split("/") | last blueprint: '"githubRepository"' properties: url: .__repository.html_url + "/tree/" + .__repository.default_branch + "/" + .folder.path readme: .folder.__includedFiles["README.md"] ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | ------- | ----------------------------------------------------------------------------------------------------------- | | `includedFiles` | `array` | File paths to fetch and attach to the raw data under `__includedFiles`. E.g. \["README.md", "CODEOWNERS"] | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `folders` | `array` | Folder definitions (path and repos) to ingest. | BlueprintJSON Create in Port ``` { "identifier": "githubUser", "title": "Github User", "icon": "Microservice", "schema": { "properties": { "email": { "title": "Email", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "user": { "title": "User", "target": "user", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: user selector: query: "true" port: entity: mappings: identifier: .login | gsub("\\["; "-") | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "") title: if (.name != "" and .name) then .name else .login end blueprint: '"githubUser"' relations: user: .email ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ----------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `includeSAMLEmail` | `boolean` | When enabled, the integration will enrich exported GitHub users with `__SAMLEmail` populated from the organization's SAML external identity (nameId). If no SAML identity is found for a user, `__SAMLEmail` will be null. Default: `false`. | | `includeVerifiedDomainEmails` | `boolean` | When enabled, the integration will include `organizationVerifiedDomainEmails` on exported GitHub users — a list of email addresses matching the organization's verified domains. Requires GitHub Enterprise Cloud with at least one verified domain. Default: `false`. | BlueprintJSON Create in Port ``` { "identifier": "githubTeam", "title": "GitHub Team", "icon": "Github", "schema": { "properties": { "slug": { "title": "Slug", "type": "string" }, "description": { "title": "Description", "type": "string" }, "link": { "title": "Link", "icon": "Link", "type": "string", "format": "url" }, "permission": { "title": "Permission", "type": "string" }, "notification_setting": { "title": "Notification Setting", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "members": { "title": "Members", "target": "githubUser", "required": false, "many": true } } } ``` Default mappingYAML ``` - kind: team selector: query: 'true' members: true port: entity: mappings: identifier: .databaseId | tostring title: .name blueprint: '"githubTeam"' properties: slug: .slug description: .description link: .url permission: .permission notification_setting: .notificationSetting relations: members: '[.members.nodes[].login]' ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `includeSAMLEmail` | `boolean` | When enabled, the integration will enrich exported GitHub users with `__SAMLEmail` populated from the organization's SAML external identity (nameId). If no SAML identity is found for a user, `__SAMLEmail` will be null. Default: `false`. | | `members` | `boolean` | Include team members in the exported data. Default: `true`. | | `include_external_group` | `boolean` | Include the external IdP group linked to each team via GET /orgs/{org}/teams/{slug}/external-groups. Only available for organizations using GitHub Enterprise with managed user accounts and external SCIM. Default: `false`. | BlueprintJSON Create in Port ``` { "identifier": "githubWorkflow", "title": "Workflow", "icon": "Github", "schema": { "properties": { "path": { "title": "Path", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": [ "active", "deleted", "disabled_fork", "disabled_inactivity", "disabled_manually" ], "enumColors": { "active": "green", "deleted": "red" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "deletedAt": { "title": "Deleted At", "type": "string", "format": "date-time" }, "link": { "title": "Link", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: workflow selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: ".__repository + (.id|tostring)" title: ".name" blueprint: '"githubWorkflow"' properties: path: ".path" status: ".state" createdAt: ".created_at" updatedAt: ".updated_at" link: ".html_url" relations: repository: ".__repository" ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubWorkflowRun", "title": "Workflow Run", "icon": "Github", "schema": { "properties": { "name": { "title": "Name", "type": "string" }, "triggeringActor": { "title": "Triggering Actor", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": [ "completed", "action_required", "cancelled", "startup_failure", "failure", "neutral", "skipped", "stale", "success", "timed_out", "in_progress", "queued", "requested", "waiting" ], "enumColors": { "queued": "yellow", "in_progress": "yellow", "success": "green", "failure": "red" } }, "conclusion": { "title": "Conclusion", "type": "string", "enum": [ "completed", "action_required", "cancelled", "startup_failure", "failure", "neutral", "skipped", "stale", "success", "timed_out", "in_progress", "queued", "requested", "waiting" ], "enumColors": { "queued": "yellow", "in_progress": "yellow", "success": "green", "failure": "red" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "runStartedAt": { "title": "Run Started At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "runNumber": { "title": "Run Number", "type": "number" }, "runAttempt": { "title": "Run Attempts", "type": "number" }, "link": { "title": "Link", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "workflow": { "target": "githubWorkflow", "required": true, "many": false } } } ``` Default mappingYAML ``` - kind: workflow-run selector: query: .status != "completed" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: ".repository.name + (.id|tostring)" title: ".display_title" blueprint: '"githubWorkflowRun"' properties: name: ".name" triggeringActor: ".triggering_actor.login" status: ".status" conclusion: ".conclusion" createdAt: ".created_at" runStartedAt: ".run_started_at" updatedAt: ".updated_at" deletedAt: ".deleted_at" runNumber: ".run_number" runAttempt: ".run_attempt" link: ".html_url" relations: workflow: ".repository.name + (.workflow_id|tostring)" ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `statuses` | `array` | Filter workflow runs by status or conclusion. Accepts a list of values. Each additional status value results in one extra API call per workflow — keep the list small. | | `since` | `integer` | Only fetch workflow runs created within the last N days. Takes precedence over sinceDate when both are set. Ignored during incremental sync. | | `since_date` | `string` | Only fetch workflow runs created on or after this date. Accepts ISO 8601 format (e.g. 2024-01-01 or 2024-01-01T00:00:00Z). Ignored if since is set. Ignored during incremental sync. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubBranch", "title": "Branch", "icon": "Github", "schema": { "properties": { "is_protected": { "title": "Is branch protected", "type": "boolean", "icon": "DefaultProperty" }, "commit_sha": { "title": "Commit SHA", "type": "string", "icon": "DefaultProperty" }, "commit_url": { "title": "Commit URL", "type": "string", "format": "url" }, "is_default_branch": { "title": "Is default branch", "type": "boolean", "icon" : "DefaultProperty" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": false } } } ``` Default mappingYAML ``` - kind: branch selector: query: 'true' port: entity: mappings: identifier: .__repository + "_" + .name title: .__repository + " " + .name blueprint: '"githubBranch"' properties: is_protected: .protected commit_sha: .commit.sha commit_url: .commit.url is_default_branch: .__is_default_branch relations: repository: .__repository ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `detailed` | `boolean` | Whether to include the latest commit details for each branch. Default: `false`. | | `defaultBranchOnly` | `boolean` | Sync only the repository's default branch; overrides branchNames if set. Default: `false`. | | `protectionRules` | `boolean` | Whether to include branch protection rules for each branch. Default: `false`. | | `branchNames` | `array` | Branches to fetch (e.g. \['main', 'develop']). | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubRepoEnvironment", "title": "Deployment Environments", "icon": "Environment", "schema": { "properties": { "url": { "icon": "DefaultProperty", "title": "URL", "type": "string", "format": "url" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time", "icon": "DefaultProperty" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "protectedBranches": { "title": "Protected Branches", "type": "boolean" }, "customBranchPolicies": { "title": "Custom Branch Policies", "type": "boolean" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "target": "githubRepository", "required": true, "many": false } } } ``` Default mappingYAML ``` - kind: environment selector: query: "true" port: entity: mappings: identifier: .__repository + "-" + .name title: .name blueprint: '"githubRepoEnvironment"' properties: url: .html_url customBranchPolicies: .deployment_branch_policy.custom_branches_policies protectedBranches: .deployment_branch_policy.protected_branches createdAt: .created_at updatedAt: .updated_at relations: repository: .__repository ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `includeVariables` | `boolean` | Include environment variables (fetched via the variables REST API) as \_\_variables on each environment. Default: `false`. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubDeployment", "title": "Deployment", "icon": "Deployment", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "ref": { "title": "Ref", "type": "string" }, "sha": { "title": "Sha", "type": "string" }, "transientEnvironment": { "title": "Transient Running Service", "type": "boolean" }, "productionEnvironment": { "title": "Production Running Service", "type": "boolean" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "url": { "title": "URL", "type": "string", "icon": "Link", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "deployment_environment": { "title": "Deployment Environment", "target": "githubRepoEnvironment", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: deployment selector: query: "true" port: entity: mappings: identifier: .__repository + "-" + (.id|tostring) title: .task + "-" + .environment blueprint: '"githubDeployment"' properties: description: .description ref: .ref sha: .sha productionEnvironment: .production_environment transientEnvironment: .transient_environment createdAt: .created_at url: .url relations: deployment_environment: .__repository + "-" + .environment ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ----------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `task` | `string` | Filter deployments by task name (e.g. deploy, deploy:migrations). | | `environment` | `string` | Filter deployments by environment name (e.g. staging, production). | | `enrichWithFirstCommit` | `boolean` | When enabled, each deployment is enriched with the earliest commit shipped since the previous deployment to the same environment: \_\_firstCommit (\_\_sha, \_\_timestamp in UTC) plus a deployment-level \_\_commitCount. Defaults to false. Default: `false`. | | `createdSince` | `string` | Only include deployments created after this date (ISO 8601). Ignored during incremental sync. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubDeploymentStatus", "title": "Deployment Status", "icon": "Deployment", "schema": { "properties": { "state": { "title": "State", "type": "string", "enum": ["success", "failure", "pending", "in_progress", "queued", "error"], "enumColors": { "success": "green", "failure": "red", "pending": "yellow", "in_progress": "blue", "queued": "purple", "error": "red" } }, "description": { "title": "Description", "type": "string" }, "environment": { "title": "Environment", "type": "string" }, "logUrl": { "title": "Log URL", "type": "string", "format": "url" }, "environmentUrl": { "title": "Environment URL", "type": "string", "format": "url" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "deployment": { "title": "Deployment", "target": "githubDeployment", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: deployment-status selector: query: "true" port: entity: mappings: identifier: .__repository + "-" + (.__deployment_id|tostring) + "-" + (.id|tostring) title: .state + " - " + .environment blueprint: '"githubDeploymentStatus"' properties: state: .state description: .description environment: .environment logUrl: .log_url environmentUrl: .environment_url createdAt: .created_at updatedAt: .updated_at relations: deployment: .__repository + "-" + (.__deployment_id|tostring) ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `task` | `string` | Filter deployment statuses by deployment task name (e.g. deploy, deploy:migrations). | | `environment` | `string` | Filter deployment statuses by deployment environment name (e.g. staging, production). | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubRelease", "title": "Release", "icon": "Github", "schema": { "properties": { "release_creation_time": { "icon": "DefaultProperty", "type": "string", "title": "Release creation time", "format": "date-time" }, "author": { "type": "string", "title": "Author" }, "description": { "type": "string", "title": "Description" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false }, "tag": { "title": "Tag", "target": "githubTag", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: release selector: query: 'true' port: entity: mappings: identifier: .name title: .name blueprint: '"githubRelease"' properties: author: .author.login description: .body release_creation_time: .created_at relations: tag: .tag_name repository: .__repository ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `createdSince` | `string` | Only include releases created after this date (ISO 8601). Ignored during incremental sync. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubTag", "title": "Tag", "icon": "Github", "schema": { "properties": { "commit_sha": { "icon": "DefaultProperty", "type": "string", "title": "Commit sha" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false } } } ``` Default mappingYAML ``` - kind: tag selector: query: 'true' port: entity: mappings: identifier: .name title: .name blueprint: '"githubTag"' properties: commit_sha: .commit.sha relations: repository: .__repository ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubDependabotAlert", "title": "Dependabot Alert", "icon": "Github", "schema": { "properties": { "severity": { "title": "Severity", "type": "string", "enum": ["low", "medium", "high", "critical"], "enumColors": { "low": "yellow", "medium": "orange", "high": "red", "critical": "red" }, "icon": "DefaultProperty" }, "state": { "title": "State", "type": "string", "enum": ["auto_dismissed", "dismissed", "fixed", "open"], "enumColors": { "auto_dismissed": "green", "dismissed": "green", "fixed": "green", "open": "red" }, "icon": "DefaultProperty" }, "packageName": { "icon": "DefaultProperty", "title": "Package Name", "type": "string" }, "packageEcosystem": { "title": "Package Ecosystem", "type": "string" }, "manifestPath": { "title": "Manifest Path", "type": "string" }, "scope": { "title": "Scope", "type": "string" }, "ghsaID": { "title": "GHSA ID", "type": "string" }, "cveID": { "title": "CVE ID", "type": "string" }, "url": { "title": "URL", "type": "string", "format": "url" }, "references": { "icon": "Vulnerability", "title": "References", "type": "array", "items": { "type": "string", "format": "url" } }, "alertCreatedAt": { "icon": "DefaultProperty", "type": "string", "title": "Alert Created At", "format": "date-time" }, "alertUpdatedAt": { "icon": "DefaultProperty", "type": "string", "title": "Alert Updated At", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": false } } } ``` Default mappingYAML ``` - kind: dependabot-alert selector: query: "true" states: ["open", "fixed"] # other options: auto_fixed, dismissed port: entity: mappings: identifier: .__repository + "/" + (.number | tostring) title: .number | tostring blueprint: '"githubDependabotAlert"' properties: state: .state severity: .security_advisory.severity packageName: .dependency.package.name packageEcosystem: .dependency.package.ecosystem manifestPath: .dependency.manifest_path scope: .dependency.scope ghsaID: .security_advisory.ghsa_id cveID: .security_advisory.cve_id url: .html_url references: "[.security_advisory.references[].url]" alertCreatedAt: .created_at alertUpdatedAt: .updated_at relations: repository: .__repository ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `states` | `array` | Filter alerts by states (e.g. \['auto\_dismissed', 'dismissed', 'fixed', 'open']). Default: `["open"]`. | | `severity` | `array` | Filter alerts by severity (e.g. \['low', 'medium']). | | `ecosystems` | `array` | Filter alerts by package ecosystem (e.g. \['npm', 'pip']). | | `updatedSince` | `string` | Only include alerts updated after this date (ISO 8601). Ignored during incremental sync. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubCodeScanAlert", "title": "Code Scan Alerts", "icon": "Microservice", "schema": { "properties": { "description": { "title": "Alert description", "type": "string" }, "severity": { "title": "Alert severity", "type": "string" }, "tags": { "items": { "type": "string" }, "title": "Alert tags", "type": "array" }, "url": { "title": "alert URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": false } } } ``` Default mappingYAML ``` - kind: code-scanning-alerts selector: query: "true" state: "dismissed" # other options: open, closed, fixed port: entity: mappings: identifier: .__repository + "/" + (.number | tostring) title: .rule.name blueprint: '"githubCodeScanAlert"' properties: state: .state severity: .rule.severity tags: .rule.tags description: .rule.description url: .html_url relations: repository: .__repository ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `state` | `string` | Filter alerts by state (e.g. 'open', 'closed', 'dismissed', 'fixed'). Default: `"open"`. | | `severity` | `string` | Filter alerts by severity level (e.g. 'critical', 'high', 'medium', 'low', 'warning', 'note', 'error'). | | `updatedSince` | `string` | Only include alerts updated after this date (ISO 8601). Ignored during incremental sync. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | BlueprintJSON Create in Port ``` { "identifier": "githubSecretScanAlert", "title": "Secret Scan Alerts", "icon": "Microservice", "schema": { "properties": { "number": { "title": "Alert Number", "type": "number" }, "state": { "title": "Alert State", "type": "string" }, "secret_type": { "title": "Secret Type", "type": "string" }, "secret": { "title": "Secret", "type": "string" }, "url": { "title": "alert URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": true } } } ``` Default mappingYAML ``` - kind: secret-scanning-alerts selector: query: 'true' state: all hideSecret: false port: entity: mappings: identifier: .__repository + "/" + (.number | tostring) title: .number | tostring blueprint: '"githubSecretScanAlert"' properties: state: .state secret_type: .rule.secret_type url: .url relations: repository: .__repository ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `excludeArchived` | `boolean` | When enabled, archived repositories are excluded during repository discovery for this kind, before any per-repository data is fetched. Does not affect explicitly listed `repos` entries, which are always included regardless of archived status. Default: `false`. | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `repoSearch` | `object` | Filter which repositories are ingested using GitHub's repository search API. **Read the limitations before using this selector:** [Port docs](https://docs.port.io/build-your-software-catalog/sync-data-to-catalog/git/github-ocean/capabilities/#limitations-1). | | `state` | `string` | Filter alerts by state (open, resolved, all). Default: `"open"`. | | `hideSecret` | `boolean` | Control whether the secret content is included. Default: `true`. | `repoSearch` parameters | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | `query` | `string` | GitHub repository search query (e.g. 'org:myorg' or 'topic:security'). | Authentication requirement Classic PAT required GitHub Packages is not supported when authenticating with a GitHub App or a fine-grained PAT. This is a [GitHub-side limitation](https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages#about-scopes-and-permissions-for-package-registries): the Packages API only accepts a classic personal access token (PAT). To ingest the `package` kind, authenticate with a [classic PAT](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md?auth=pat) that includes the `read:packages` scope. BlueprintJSON Create in Port ``` { "identifier": "githubPackage", "title": "Package", "icon": "Package", "schema": { "properties": { "packageType": { "type": "string", "title": "Package type", "enum": ["container", "npm", "maven", "rubygems", "nuget", "docker"] }, "visibility": { "type": "string", "title": "Visibility", "enum": ["private", "public", "internal"] }, "url": { "type": "string", "title": "URL", "format": "url" }, "createdAt": { "type": "string", "title": "Created at", "format": "date-time" }, "updatedAt": { "type": "string", "title": "Updated at", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false }, "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` MappingYAML ``` - kind: package selector: query: "true" packageTypes: - container port: entity: mappings: identifier: .__organization + "/" + .name title: .name blueprint: '"githubPackage"' properties: packageType: .package_type visibility: .visibility url: .html_url createdAt: .created_at updatedAt: .updated_at relations: repository: .__repository organization: .__organization ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `packageTypes` | `array` | GitHub package types to ingest. Default: `["container"]`. | | `visibility` | `string` | Filter packages by visibility. When unset, packages of all visibilities are ingested. | | `includeVersions` | `boolean` | Fetch package versions (tags and digests) and attach them as `__versions`. Enabling this consumes additional GitHub API rate limit and may slow down the resync. Default: `false`. | | `maxVersions` | `integer` | Maximum number of versions to fetch per package, newest first. Only used when includeVersions is true. A larger version history consumes additional GitHub API rate limit and may slow down the resync. Default: `10`. | ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 GitHub 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 GitHub 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If GitHub is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that GitHub can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Incremental sync[​](#incremental-sync "Direct link to Incremental sync") In addition to the standard full resync, this integration supports [incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync/.md) for the following resource kinds: * `repository` * `pull-request` * `workflow-run` * `deployment` * `release` * `code-scanning-alerts` * `dependabot-alert` * `issue` Any other kind in your mapping is not fetched incrementally, and continues to be kept up to date by the full resync. See [Incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync/.md) for how it works, its current limitations, and how to enable it for your organization. ## Capabilities[​](#capabilities "Direct link to Capabilities") The GitHub Ocean integration supports a rich set of data ingestion capabilities. See the [capabilities page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md) for full details on each feature. Key capabilities include: * **[Ingest Git objects](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-git-objects)** — Automatically ingest GitHub resources (repositories, pull requests, issues, and more) based on real-time events. * **[Ingest organizations](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-organizations)** — Sync organization-level data as parent entities for repositories and teams (available from v3.0.0-beta). * **[Enrich entities with file contents](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#enrich-entities-with-file-contents)** — Attach files from repositories using the `includedFiles` selector and map them with JQ via `.__includedFiles` instead of the deprecated `file://` prefix. * **[Ingest files from repositories](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-files-from-your-repositories)** — Fetch JSON and YAML files from repositories and create Port entities from their contents. * **[Ingest agent skills](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-agent-skills)** — Discover `SKILL.md` Agent Skills with the skill kind (instructions only), or use the file kind to ingest references and assets. * **[Ingest agent plugins](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-agent-plugins)** — Detect agent plugin packages per repository and normalize provider support. * **[Ingest MCP servers](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-mcp-servers)** - Discover MCP servers from `mcp.json` files in your repositories and map them into an `mcpServer` catalog blueprint. * **[Repository search API](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-repositories-via-search-api)** — Use GitHub's repository search API for granular repository filtering when ingesting data. * **[Parallel event processing](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#configure-parallel-processing)** — Configure multiple async workers to handle GitHub webhook events in parallel, preventing race conditions while maintaining event ordering. * **[Personal account ingestion](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-the-authenticated-users-personal-account)** — Optionally ingest the authenticated user's personal GitHub account as a pseudo-organization. * **[Incremental sync](#incremental-sync)** - Sync recently created or updated resources on a short interval, in parallel to the full resync. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Examples[​](#examples "Direct link to Examples") For blueprint and configuration examples across all supported resource kinds, see the [GitHub Ocean examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/examples/.md). To view and test the integration's mapping against live API responses, use the **jq playground** in your [connectors page](https://app.port.io/settings/data-sources). ## Relevant guides[​](#relevant-guides "Direct link to Relevant guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=GitHub). --- # Capabilities This page describes the data ingestion capabilities of Port's GitHub integration. ## Configure parallel processing[​](#configure-parallel-processing "Direct link to Configure parallel processing") Configure multiple workers to handle GitHub webhook events in parallel groups, preventing race conditions while maintaining event ordering for related resources. Starting with Ocean 0.27, a new parameter was added to the configuration, which adjusts the number of async workers spawned for webhook events. Set `event_workers_count: 4` in your config (default: 1) to enable parallel group processing. When `event_workers_count > 1`, related GitHub events get grouped together: * All PR events (opened, review, status) for PR #123 → same processing group. * All issue events for issue #456 → same processing group. * Push events grouped by commit SHA. **Event grouping behavior (click to expand)** **Supported event types:** * **Pull requests**: Grouped by PR number (`pull_request`, `pull_request_review`, `pull_request_review_comment`). * **Issues**: Grouped by issue number (`issues`, `issue_comment`). * **Pushes**: Grouped by commit SHA (`push`). * **Releases**: Grouped by release ID (`release`). * **Workflow runs**: Grouped by run ID (`workflow_run`). * **Status checks**: Grouped by commit SHA (`status`). **Fallback**: If the event type is not recognized, the payload will be scanned for the first `number`, `id`, or `sha` field. Processing guarantees Events within the same group are processed sequentially to maintain order and prevent conflicts. Different groups can be processed in parallel across workers. With `event_workers_count: 1`, all events are processed sequentially using a simple queue. ### Resync rate-limit reservation[​](#resync-rate-limit-reservation "Direct link to Resync rate-limit reservation") During a data resync, the integration shares your GitHub REST rate limit with live webhook events and Port actions (for example, workflow dispatches). Large resyncs can exhaust the available quota, preventing webhooks and actions from running. The `resync_ratelimit_reservation_threshold` parameter reserves part of the rate limit for these real-time operations. When rate-limit utilization reaches the configured threshold, resync processing pauses until the GitHub rate limit resets. Webhooks and actions continue to run normally. #### Configuration[​](#configuration "Direct link to Configuration") Set this value to an integer between 0 and 100, representing the utilization percentage that triggers a resync pause. * **Default:** `95` (resync pauses at 95% utilization, leaving 5% available for webhooks and actions). * Values outside the 0–100 range are ignored and the default is used. **Example: lowering the reservation threshold (click to expand)** ``` resyncRatelimitReservationThreshold: 80 ``` With this configuration, resync pauses at 80% utilization, leaving 20% of the rate limit available for webhooks and actions. This can be useful for organizations with high webhook volume or frequent workflow dispatches during resyncs. Note: This threshold only controls when resyncs pause. The integration's built-in rate-limit protection still pauses all requests when GitHub's rate limit is nearly exhausted to prevent `403` and `429` errors. ## Ingest Git objects[​](#ingest-git-objects "Direct link to Ingest Git objects") Using Port's GitHub integration, you can automatically ingest GitHub resources into Port based on real-time events. The app allows you to ingest a variety of object resources provided by the GitHub API, including organizations, repositories, pull requests, workflows and more. It also allows you to perform "extract, transform, load (ETL)" on data from the GitHub API into the desired software catalog data model. The GitHub integration uses a YAML configuration file to describe the ETL process to load data into the developer portal. This approach provides a flexible and powerful way to model your Git data without being overly opinionated or complex. ## Ingest organizations[​](#ingest-organizations "Direct link to Ingest organizations") The GitHub integration automatically syncs organization-level data (available from **v3.0.0-beta**). Organization as parent entity Organizations serve as parent entities for repositories, teams, and other GitHub resources, helping you model your organizational structure in Port. Organization entities also update through live events when you rename or delete an organization in GitHub. Requires GitHub integration version `6.14.0` or later. Port identifies an organization entity by its login, so renaming an organization replaces the entity. The integration deletes the entity for the old login and creates a new one under the new login. That event does not re-ingest repositories, teams, and other related resources, so their relation still points to the old login. To restore those relations, resync the integration from your [connectors](https://app.port.io/settings/data-sources) page. Deleting an organization removes its organization entity immediately. Entities ingested from that organization, such as repositories and teams, remain in your catalog until the next resync. ## Enrich entities with file contents[​](#enrich-entities-with-file-contents "Direct link to Enrich entities with file contents") You can enrich repository, folder, and file entities with the contents of specific files from your repositories. This is useful for including README files, CODEOWNERS, or other documentation directly in your entity properties. To do this, use the `includedFiles` selector to specify which files to fetch, and then reference them in your mapping using `.__includedFiles[""]`. ``` resources: - kind: repository selector: query: 'true' includedFiles: - README.md - CODEOWNERS port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] codeowners: .__includedFiles["CODEOWNERS"] url: .html_url ``` Deprecation notice The `file://` prefix in mappings is deprecated and will be removed in a future version. Use the `includedFiles` selector instead. The `file://` prefix will continue to work but will show deprecation warnings in the logs. The `includedFiles` feature: * Fetches file contents from the repository's default branch (or the branch you set on the selector for folder and file entities). * Stores file contents in the `__includedFiles` object in the raw data. * Handles missing files gracefully (stores `null` if a file does not exist). * Works with repository, folder, and file entity types. For **folder** entities, you can list `includedFiles` under each `folders` entry so paths resolve relative to each matched folder (for example `README.md` inside `apps/service-a/`). Map those with `.folder.__includedFiles["README.md"]`. You can also attach repository-root files to folder entities by placing `includedFiles` next to `folders` at the resource level and mapping with `.__includedFiles["README.md"]`. See the [monorepo example](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/examples/.md#repositories-and-monorepos) for both patterns. If you still map file contents with the legacy `file://` prefix from Port's previous GitHub app, see [Kind mapping changes in the migration from GitHub app guide](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/migration/key-changes/.md#kind-mapping-changes). Under **Repository relationships**, expand the **Existing configuration** and **New configuration** details to compare YAML that replaces `file://` with `includedFiles` and `.__includedFiles`. ## Ingest files from your repositories[​](#ingest-files-from-your-repositories "Direct link to Ingest files from your repositories") Port allows you to fetch `JSON` and `YAML` files from your repositories, and create entities from them in your software catalog. This is done using the `file` kind in your GitHub mapping configuration. For example, say you want to manage your `package.json` files in Port. One option is to create a `manifest` blueprint, with each of its entities representing a `package.json` file. The following configuration fetches all `package.json` files from "MyRepo" and "MyOtherRepo", and creates an entity for each of them, based on the `manifest` blueprint: **Organization and repository filtering:** Both `organization` and `repos` in file selectors are optional. You can: * Specify only `organization`: scan all repositories in that organization (respecting `repositoryType`). * Specify only `repos`: scan only those repositories across all accessible organizations. * Omit both: scan all repositories accessible to your credentials. When scanning broadly, the integration scope depends on your credentials: * With GitHub App or fine‑grained PAT: all repositories in the installed/specified organization. * With classic PAT: all repositories across all organizations the token can access. Use `repositoryType` and precise `path` patterns to reduce scope. **Note:** Omitting `organization` and `repos` will scan all accessible repositories. For large orgs, expect longer resyncs and higher GitHub API usage. Narrow scope with `repos`, `repositoryType`, and specific `path` patterns. **Package file mapping example (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' organization: my-org # Optional, omit to scan all orgs in scope # Optional: use `repos` to filter repositories/branches (omit to scan all repos) repos: - name: MyRepo branch: main - name: MyOtherRepo branch: main port: entity: mappings: identifier: .path title: .name blueprint: '"manifest"' properties: project_name: .content.name project_version: .content.version license: .content.license ``` **Test your mapping:** After adding the `file` kind to your mapping configuration, click on the `Resync` button. When you open the mapping configuration again, you will see real examples of files fetched from your GitHub organization. This will help you see what data is available to use in your `jq` expressions. Click on the **Test mapping** button to test your mapping against the example data. In any case, the structure of the available data looks like this: **Available data example (click to expand)** ``` { "content": { "name": "code-oss-dev-build", "version": "1.0.0", "license": "MIT", "devDependencies": { "@azure/core-auth": "^1.9.0", "@azure/cosmos": "^3", "@azure/identity": "^4.2.1", "@azure/msal-node": "^2.16.1", "@azure/storage-blob": "^12.25.0", "@electron/get": "^2.0.0", "@types/ansi-colors": "^3.2.0", "@types/byline": "^4.2.32", "@types/debounce": "^1.0.0", "@types/debug": "^4.1.5", "@types/fancy-log": "^1.3.0", "@types/fs-extra": "^9.0.12", "@types/glob": "^7.1.1", "@types/gulp": "^4.0.17", "@types/gulp-filter": "^3.0.32", "@types/gulp-gzip": "^0.0.31", "@types/gulp-json-editor": "^2.2.31", "@types/gulp-rename": "^0.0.33", "@types/gulp-sort": "^2.0.4", "@types/gulp-sourcemaps": "^0.0.32", "@types/jws": "^3.2.10", "@types/mime": "0.0.29", "@types/minimatch": "^3.0.3", "@types/minimist": "^1.2.1", "@types/mocha": "^9.1.1", "@types/node": "22.x", "@types/pump": "^1.0.1", "@types/rimraf": "^2.0.4", "@types/through": "^0.0.29", "@types/through2": "^2.0.36", "@types/workerpool": "^6.4.0", "@types/xml2js": "0.0.33", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/ripgrep": "^1.15.13", "@vscode/vsce": "2.20.1", "byline": "^5.0.0", "debug": "^4.3.2", "electron-osx-sign": "^0.4.16", "esbuild": "0.25.5", "extract-zip": "^2.0.1", "gulp-merge-json": "^2.1.1", "gulp-sort": "^2.0.0", "jsonc-parser": "^2.3.0", "jws": "^4.0.0", "mime": "^1.4.1", "source-map": "0.6.1", "ternary-stream": "^3.0.0", "through2": "^4.0.2", "tree-sitter": "^0.22.4", "vscode-universal-bundler": "^0.1.3", "workerpool": "^6.4.0", "yauzl": "^2.10.0", "zx": "8.5.0" }, "type": "commonjs", "scripts": { "compile": "../node_modules/.bin/tsc -p tsconfig.build.json", "watch": "../node_modules/.bin/tsc -p tsconfig.build.json --watch", "npmCheckJs": "../node_modules/.bin/tsc --noEmit" }, "optionalDependencies": { "tree-sitter-typescript": "^0.23.2", "vscode-gulp-watch": "^5.0.3" } }, "repository": { "id": 1006465568, "node_id": "R_kgDOO_1yIA", "name": "vscode", "full_name": "port-gh-app-dev/vscode", "private": false, "owner": { "login": "port-gh-app-dev", "id": 216844958, "node_id": "O_kgDODOzKng", "avatar_url": "https://avatars.githubusercontent.com/u/216844958?v=4", "gravatar_id": "", "url": "https://api.github.com/users/port-gh-app-dev", "html_url": "https://github.com/port-gh-app-dev", "followers_url": "https://api.github.com/users/port-gh-app-dev/followers", "following_url": "https://api.github.com/users/port-gh-app-dev/following{/other_user}", "gists_url": "https://api.github.com/users/port-gh-app-dev/gists{/gist_id}", "starred_url": "https://api.github.com/users/port-gh-app-dev/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/port-gh-app-dev/subscriptions", "organizations_url": "https://api.github.com/users/port-gh-app-dev/orgs", "repos_url": "https://api.github.com/users/port-gh-app-dev/repos", "events_url": "https://api.github.com/users/port-gh-app-dev/events{/privacy}", "received_events_url": "https://api.github.com/users/port-gh-app-dev/received_events", "type": "Organization", "user_view_type": "public", "site_admin": false }, "html_url": "https://github.com/port-gh-app-dev/vscode", "description": "Visual Studio Code", "fork": true, "url": "https://api.github.com/repos/port-gh-app-dev/vscode", "forks_url": "https://api.github.com/repos/port-gh-app-dev/vscode/forks", "keys_url": "https://api.github.com/repos/port-gh-app-dev/vscode/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/port-gh-app-dev/vscode/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/port-gh-app-dev/vscode/teams", "hooks_url": "https://api.github.com/repos/port-gh-app-dev/vscode/hooks", "issue_events_url": "https://api.github.com/repos/port-gh-app-dev/vscode/issues/events{/number}", "events_url": "https://api.github.com/repos/port-gh-app-dev/vscode/events", "assignees_url": "https://api.github.com/repos/port-gh-app-dev/vscode/assignees{/user}", "branches_url": "https://api.github.com/repos/port-gh-app-dev/vscode/branches{/branch}", "tags_url": "https://api.github.com/repos/port-gh-app-dev/vscode/tags", "blobs_url": "https://api.github.com/repos/port-gh-app-dev/vscode/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/port-gh-app-dev/vscode/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/port-gh-app-dev/vscode/git/refs{/sha}", "trees_url": "https://api.github.com/repos/port-gh-app-dev/vscode/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/port-gh-app-dev/vscode/statuses/{sha}", "languages_url": "https://api.github.com/repos/port-gh-app-dev/vscode/languages", "stargazers_url": "https://api.github.com/repos/port-gh-app-dev/vscode/stargazers", "contributors_url": "https://api.github.com/repos/port-gh-app-dev/vscode/contributors", "subscribers_url": "https://api.github.com/repos/port-gh-app-dev/vscode/subscribers", "subscription_url": "https://api.github.com/repos/port-gh-app-dev/vscode/subscription", "commits_url": "https://api.github.com/repos/port-gh-app-dev/vscode/commits{/sha}", "git_commits_url": "https://api.github.com/repos/port-gh-app-dev/vscode/git/commits{/sha}", "comments_url": "https://api.github.com/repos/port-gh-app-dev/vscode/comments{/number}", "issue_comment_url": "https://api.github.com/repos/port-gh-app-dev/vscode/issues/comments{/number}", "contents_url": "https://api.github.com/repos/port-gh-app-dev/vscode/contents/{+path}", "compare_url": "https://api.github.com/repos/port-gh-app-dev/vscode/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/port-gh-app-dev/vscode/merges", "archive_url": "https://api.github.com/repos/port-gh-app-dev/vscode/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/port-gh-app-dev/vscode/downloads", "issues_url": "https://api.github.com/repos/port-gh-app-dev/vscode/issues{/number}", "pulls_url": "https://api.github.com/repos/port-gh-app-dev/vscode/pulls{/number}", "milestones_url": "https://api.github.com/repos/port-gh-app-dev/vscode/milestones{/number}", "notifications_url": "https://api.github.com/repos/port-gh-app-dev/vscode/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/port-gh-app-dev/vscode/labels{/name}", "releases_url": "https://api.github.com/repos/port-gh-app-dev/vscode/releases{/id}", "deployments_url": "https://api.github.com/repos/port-gh-app-dev/vscode/deployments", "created_at": "2025-06-22T10:36:32Z", "updated_at": "2025-07-01T12:24:24Z", "pushed_at": "2025-07-01T12:24:14Z", "git_url": "git://github.com/port-gh-app-dev/vscode.git", "ssh_url": "git@github.com:port-gh-app-dev/vscode.git", "clone_url": "https://github.com/port-gh-app-dev/vscode.git", "svn_url": "https://github.com/port-gh-app-dev/vscode", "homepage": "https://code.visualstudio.com", "size": 957812, "stargazers_count": 0, "watchers_count": 0, "language": "TypeScript", "has_issues": false, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "has_discussions": false, "forks_count": 0, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 0, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "allow_forking": true, "is_template": false, "web_commit_signoff_required": false, "topics": [], "visibility": "public", "forks": 0, "open_issues": 0, "watchers": 0, "default_branch": "main", "permissions": { "admin": false, "maintain": false, "push": false, "triage": false, "pull": false }, "temp_clone_token": "", "allow_squash_merge": true, "allow_merge_commit": true, "allow_rebase_merge": true, "allow_auto_merge": false, "delete_branch_on_merge": false, "allow_update_branch": false, "use_squash_pr_title_as_default": false, "squash_merge_commit_message": "COMMIT_MESSAGES", "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", "merge_commit_message": "PR_TITLE", "merge_commit_title": "MERGE_MESSAGE", "custom_properties": {}, "organization": { "login": "port-gh-app-dev", "id": 216844958, "node_id": "O_kgDODOzKng", "avatar_url": "https://avatars.githubusercontent.com/u/216844958?v=4", "gravatar_id": "", "url": "https://api.github.com/users/port-gh-app-dev", "html_url": "https://github.com/port-gh-app-dev", "followers_url": "https://api.github.com/users/port-gh-app-dev/followers", "following_url": "https://api.github.com/users/port-gh-app-dev/following{/other_user}", "gists_url": "https://api.github.com/users/port-gh-app-dev/gists{/gist_id}", "starred_url": "https://api.github.com/users/port-gh-app-dev/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/port-gh-app-dev/subscriptions", "organizations_url": "https://api.github.com/users/port-gh-app-dev/orgs", "repos_url": "https://api.github.com/users/port-gh-app-dev/repos", "events_url": "https://api.github.com/users/port-gh-app-dev/events{/privacy}", "received_events_url": "https://api.github.com/users/port-gh-app-dev/received_events", "type": "Organization", "user_view_type": "public", "site_admin": false }, "parent": { "id": 41881900, "node_id": "MDEwOlJlcG9zaXRvcnk0MTg4MTkwMA==", "name": "vscode", "full_name": "microsoft/vscode", "private": false, "owner": { "login": "microsoft", "id": 6154722, "node_id": "MDEyOk9yZ2FuaXphdGlvbjYxNTQ3MjI=", "avatar_url": "https://avatars.githubusercontent.com/u/6154722?v=4", "gravatar_id": "", "url": "https://api.github.com/users/microsoft", "html_url": "https://github.com/microsoft", "followers_url": "https://api.github.com/users/microsoft/followers", "following_url": "https://api.github.com/users/microsoft/following{/other_user}", "gists_url": "https://api.github.com/users/microsoft/gists{/gist_id}", "starred_url": "https://api.github.com/users/microsoft/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/microsoft/subscriptions", "organizations_url": "https://api.github.com/users/microsoft/orgs", "repos_url": "https://api.github.com/users/microsoft/repos", "events_url": "https://api.github.com/users/microsoft/events{/privacy}", "received_events_url": "https://api.github.com/users/microsoft/received_events", "type": "Organization", "user_view_type": "public", "site_admin": false }, "html_url": "https://github.com/microsoft/vscode", "description": "Visual Studio Code", "fork": false, "url": "https://api.github.com/repos/microsoft/vscode", "forks_url": "https://api.github.com/repos/microsoft/vscode/forks", "keys_url": "https://api.github.com/repos/microsoft/vscode/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/microsoft/vscode/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/microsoft/vscode/teams", "hooks_url": "https://api.github.com/repos/microsoft/vscode/hooks", "issue_events_url": "https://api.github.com/repos/microsoft/vscode/issues/events{/number}", "events_url": "https://api.github.com/repos/microsoft/vscode/events", "assignees_url": "https://api.github.com/repos/microsoft/vscode/assignees{/user}", "branches_url": "https://api.github.com/repos/microsoft/vscode/branches{/branch}", "tags_url": "https://api.github.com/repos/microsoft/vscode/tags", "blobs_url": "https://api.github.com/repos/microsoft/vscode/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/microsoft/vscode/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/microsoft/vscode/git/refs{/sha}", "trees_url": "https://api.github.com/repos/microsoft/vscode/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/microsoft/vscode/statuses/{sha}", "languages_url": "https://api.github.com/repos/microsoft/vscode/languages", "stargazers_url": "https://api.github.com/repos/microsoft/vscode/stargazers", "contributors_url": "https://api.github.com/repos/microsoft/vscode/contributors", "subscribers_url": "https://api.github.com/repos/microsoft/vscode/subscribers", "subscription_url": "https://api.github.com/repos/microsoft/vscode/subscription", "commits_url": "https://api.github.com/repos/microsoft/vscode/commits{/sha}", "git_commits_url": "https://api.github.com/repos/microsoft/vscode/git/commits{/sha}", "comments_url": "https://api.github.com/repos/microsoft/vscode/comments{/number}", "issue_comment_url": "https://api.github.com/repos/microsoft/vscode/issues/comments{/number}", "contents_url": "https://api.github.com/repos/microsoft/vscode/contents/{+path}", "compare_url": "https://api.github.com/repos/microsoft/vscode/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/microsoft/vscode/merges", "archive_url": "https://api.github.com/repos/microsoft/vscode/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/microsoft/vscode/downloads", "issues_url": "https://api.github.com/repos/microsoft/vscode/issues{/number}", "pulls_url": "https://api.github.com/repos/microsoft/vscode/pulls{/number}", "milestones_url": "https://api.github.com/repos/microsoft/vscode/milestones{/number}", "notifications_url": "https://api.github.com/repos/microsoft/vscode/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/microsoft/vscode/labels{/name}", "releases_url": "https://api.github.com/repos/microsoft/vscode/releases{/id}", "deployments_url": "https://api.github.com/repos/microsoft/vscode/deployments", "created_at": "2015-09-03T20:23:38Z", "updated_at": "2025-07-01T15:41:46Z", "pushed_at": "2025-07-01T16:26:22Z", "git_url": "git://github.com/microsoft/vscode.git", "ssh_url": "git@github.com:microsoft/vscode.git", "clone_url": "https://github.com/microsoft/vscode.git", "svn_url": "https://github.com/microsoft/vscode", "homepage": "https://code.visualstudio.com", "size": 1030538, "stargazers_count": 174062, "watchers_count": 174062, "language": "TypeScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "has_discussions": false, "forks_count": 33404, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 11291, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "allow_forking": true, "is_template": false, "web_commit_signoff_required": false, "topics": [ "editor", "electron", "microsoft", "typescript", "visual-studio-code" ], "visibility": "public", "forks": 33404, "open_issues": 11291, "watchers": 174062, "default_branch": "main" }, "source": { "id": 41881900, "node_id": "MDEwOlJlcG9zaXRvcnk0MTg4MTkwMA==", "name": "vscode", "full_name": "microsoft/vscode", "private": false, "owner": { "login": "microsoft", "id": 6154722, "node_id": "MDEyOk9yZ2FuaXphdGlvbjYxNTQ3MjI=", "avatar_url": "https://avatars.githubusercontent.com/u/6154722?v=4", "gravatar_id": "", "url": "https://api.github.com/users/microsoft", "html_url": "https://github.com/microsoft", "followers_url": "https://api.github.com/users/microsoft/followers", "following_url": "https://api.github.com/users/microsoft/following{/other_user}", "gists_url": "https://api.github.com/users/microsoft/gists{/gist_id}", "starred_url": "https://api.github.com/users/microsoft/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/microsoft/subscriptions", "organizations_url": "https://api.github.com/users/microsoft/orgs", "repos_url": "https://api.github.com/users/microsoft/repos", "events_url": "https://api.github.com/users/microsoft/events{/privacy}", "received_events_url": "https://api.github.com/users/microsoft/received_events", "type": "Organization", "user_view_type": "public", "site_admin": false }, "html_url": "https://github.com/microsoft/vscode", "description": "Visual Studio Code", "fork": false, "url": "https://api.github.com/repos/microsoft/vscode", "forks_url": "https://api.github.com/repos/microsoft/vscode/forks", "keys_url": "https://api.github.com/repos/microsoft/vscode/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/microsoft/vscode/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/microsoft/vscode/teams", "hooks_url": "https://api.github.com/repos/microsoft/vscode/hooks", "issue_events_url": "https://api.github.com/repos/microsoft/vscode/issues/events{/number}", "events_url": "https://api.github.com/repos/microsoft/vscode/events", "assignees_url": "https://api.github.com/repos/microsoft/vscode/assignees{/user}", "branches_url": "https://api.github.com/repos/microsoft/vscode/branches{/branch}", "tags_url": "https://api.github.com/repos/microsoft/vscode/tags", "blobs_url": "https://api.github.com/repos/microsoft/vscode/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/microsoft/vscode/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/microsoft/vscode/git/refs{/sha}", "trees_url": "https://api.github.com/repos/microsoft/vscode/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/microsoft/vscode/statuses/{sha}", "languages_url": "https://api.github.com/repos/microsoft/vscode/languages", "stargazers_url": "https://api.github.com/repos/microsoft/vscode/stargazers", "contributors_url": "https://api.github.com/repos/microsoft/vscode/contributors", "subscribers_url": "https://api.github.com/repos/microsoft/vscode/subscribers", "subscription_url": "https://api.github.com/repos/microsoft/vscode/subscription", "commits_url": "https://api.github.com/repos/microsoft/vscode/commits{/sha}", "git_commits_url": "https://api.github.com/repos/microsoft/vscode/git/commits{/sha}", "comments_url": "https://api.github.com/repos/microsoft/vscode/comments{/number}", "issue_comment_url": "https://api.github.com/repos/microsoft/vscode/issues/comments{/number}", "contents_url": "https://api.github.com/repos/microsoft/vscode/contents/{+path}", "compare_url": "https://api.github.com/repos/microsoft/vscode/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/microsoft/vscode/merges", "archive_url": "https://api.github.com/repos/microsoft/vscode/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/microsoft/vscode/downloads", "issues_url": "https://api.github.com/repos/microsoft/vscode/issues{/number}", "pulls_url": "https://api.github.com/repos/microsoft/vscode/pulls{/number}", "milestones_url": "https://api.github.com/repos/microsoft/vscode/milestones{/number}", "notifications_url": "https://api.github.com/repos/microsoft/vscode/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/microsoft/vscode/labels{/name}", "releases_url": "https://api.github.com/repos/microsoft/vscode/releases{/id}", "deployments_url": "https://api.github.com/repos/microsoft/vscode/deployments", "created_at": "2015-09-03T20:23:38Z", "updated_at": "2025-07-01T15:41:46Z", "pushed_at": "2025-07-01T16:26:22Z", "git_url": "git://github.com/microsoft/vscode.git", "ssh_url": "git@github.com:microsoft/vscode.git", "clone_url": "https://github.com/microsoft/vscode.git", "svn_url": "https://github.com/microsoft/vscode", "homepage": "https://code.visualstudio.com", "size": 1030538, "stargazers_count": 174062, "watchers_count": 174062, "language": "TypeScript", "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "has_discussions": false, "forks_count": 33404, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 11291, "license": { "key": "mit", "name": "MIT License", "spdx_id": "MIT", "url": "https://api.github.com/licenses/mit", "node_id": "MDc6TGljZW5zZTEz" }, "allow_forking": true, "is_template": false, "web_commit_signoff_required": false, "topics": [ "editor", "electron", "microsoft", "typescript", "visual-studio-code" ], "visibility": "public", "forks": 33404, "open_issues": 11291, "watchers": 174062, "default_branch": "main" }, "security_and_analysis": { "secret_scanning": { "status": "disabled" }, "secret_scanning_push_protection": { "status": "disabled" }, "dependabot_security_updates": { "status": "disabled" }, "secret_scanning_non_provider_patterns": { "status": "disabled" }, "secret_scanning_validity_checks": { "status": "disabled" } }, "network_count": 33404, "subscribers_count": 1 }, "organization": "Test-Org", "branch": "main", "path": "build/package.json", "name": "package.json", "metadata": { "url": "https://api.github.com/repos/port-gh-app-dev/vscode/contents/build/package.json?ref=main", "path": "build/package.json", "size": 2106 } } ``` ### Create multiple entities from a single file[​](#create-multiple-entities-from-a-single-file "Direct link to Create multiple entities from a single file") In some cases, you may want to parse a single JSON/YAML file and create multiple entities from it. For this purpose, you can use the `itemsToParse` key in your mapping configuration. For example, say you want to track/manage a project's dependencies in Port. One option is to create a `package` blueprint, with each of its entities representing a dependency from a `package.json` file. The following configuration fetches a `package.json` file from a specific repository, and creates an entity for each of the dependencies in the file, based on the `package` blueprint: **File mapping example for multiple entities (click to expand)** ``` resources: - kind: file selector: query: 'true' files: - path: '**/package.json' organization: my-org # Optional if githubOrganization is set (required if not set) # Optional: you can target specific repositories here (omit to scan all repos) repos: - name: MyRepo branch: main port: itemsToParse: .content.dependencies | to_entries entity: mappings: # Since identifier cannot contain special characters, we are using jq to remove them identifier: >- .item.key + "_" + if (.item.value | startswith("^")) then .item.value[1:] else .item.value end title: .item.key + "@" + .item.value blueprint: '"package"' properties: package: .item.key version: .item.value relations: {} ``` Deletions require a full resync When a file parsed with `itemsToParse` is updated, live events reflect updates and additions to the parsed items, but not removals. If an item is removed from the file, its corresponding entity is not deleted until the next **full resync**. ### Multi-document YAML files[​](#multi-document-yaml-files "Direct link to Multi-document YAML files") For multi-document YAML files (a single file containing multiple YAML documents separated by `---`), `.content` will not resolve to an object, but to an array of objects. You can use one of these methods to ingest multi-document YAML files: 1. Use the `itemsToParse` key to create multiple entities from such a file (see example above). 2. Map the result to an `array` property. **Mixed YAML types:** If you have both single-document and multi-document YAML files in your repositories, you can use the `itemsToParse` key like this to handle both cases: ``` itemsToParse: .content | if type== "object" then [.] else . end ``` ### Ingest raw file content[​](#ingest-raw-file-content "Direct link to Ingest raw file content") If you need to ingest the raw content of a file without parsing it, you can use the `skipParsing` key in your file selector. This is useful when you want to store the file content as a string or YAML property. When `skipParsing` is set to `true`, the file content will be kept in its original string format instead of being parsed into a JSON/YAML object. Here's an example that ingests the raw content of a `values.yaml` file into the `content` property of a `file` entity: **File mapping example for ingesting raw content (click to expand)** ``` resources: - kind: file selector: query: 'true' files: - path: values.yaml organization: my-org # Optional, omit to scan all orgs in scope skipParsing: true repos: # Optional: omit to scan all repos - name: MyRepo branch: main port: entity: mappings: identifier: >- .repository.name + "-values" blueprint: '"file"' properties: content: .content ``` ### Limitations[​](#limitations "Direct link to Limitations") * Currently only files up to 1MB in size are supported. * Only JSON and YAML formats are automatically parsed. Other file formats can be ingested as raw files, however, some special characters in the file (such as `\n`) may be processed and not preserved. ## Ingest agent skills[​](#ingest-agent-skills "Direct link to Ingest agent skills") Port can discover [Agent Skills](https://agentskills.io/specification) (`SKILL.md` files) from your GitHub repositories using the dedicated `skill` kind. Each matched `SKILL.md` becomes one catalog entity with parsed frontmatter and markdown instructions. Skill discovery uses the same recursive git-tree matching as the file kind (glob patterns against the repository tree). Tree walks are heavier than search-based discovery on large orgs. Omitting `organization` / `repos` on a path scans every accessible repository (with a classic PAT, that can span organizations) — scope with `organization` and `repos` when you can. Configure discovery with `selector.paths` (same multi-org shape as the file kind). Defaults cover common agent skill layouts: * `.agents/skills/**/SKILL.md` * `.agent/skills/**/SKILL.md` * `.cursor/skills/**/SKILL.md` * `.claude/skills/**/SKILL.md` * `.codex/skills/**/SKILL.md` * `.github/skills/**/SKILL.md` * `.opencode/skills/**/SKILL.md` * `skills/**/SKILL.md` ``` resources: - kind: skill selector: query: 'true' paths: - path: '.cursor/skills/**/SKILL.md' organization: my-org # Optional: omit repos to scan all repositories in the organization repos: - name: my-skills-repo branch: main - path: 'skills/**/SKILL.md' organization: my-org port: entity: mappings: identifier: .__repository.full_name + "/" + .skill.skillMdPath title: .skill.name // .skill.skillMdPath blueprint: '"skill"' properties: description: .skill.description instructions: .skill.instructions path: .skill.skillMdPath root: .skill.root repo: .__repository.full_name repoUrl: .__repository.html_url branch: .__branch source: '"github"' ``` Test your mapping After adding the `skill` kind and clicking **Resync**, open the mapping again to inspect live skill examples, then use **Test mapping** to validate your JQ. **Available data example (click to expand)** ``` { "skill": { "name": "hello-skill", "description": "A minimal example skill used to test skill discovery.", "instructions": "# Hello Skill\n\nRespond with a short greeting.\n", "frontmatter": { "name": "hello-skill", "description": "A minimal example skill used to test skill discovery." }, "path": "skills/hello-skill", "skillMdPath": "skills/hello-skill/SKILL.md", "root": "skills" }, "__repository": { "full_name": "my-org/example-skills", "name": "example-skills", "html_url": "https://github.com/my-org/example-skills", "default_branch": "main" }, "__branch": "main", "__organization": "my-org" } ``` Push webhooks follow the same branch rules as the file kind: when `paths[].repos` is omitted or `repos[].branch` is unset, only the repository default branch is processed. Feature-branch `SKILL.md` edits do not live-update the catalog. The `skill` kind maps `SKILL.md` into instructions only. It does not ingest `references/` or `assets/`. If those files must land on the skill entity, use the `file` kind instead of combining kinds. For skills registry concepts and both mapping examples, see the [skills registry](/agent-management/ai-registry/skills/skills-registry/.md). ## Ingest agent plugins[​](#ingest-agent-plugins "Direct link to Ingest agent plugins") The `plugin` kind detects agent plugin packages in a repository and emits **one entity per repository**, merging provider manifests that are present (Claude, Cursor, Codex, OpenCode, and related layouts). Plugins always walk the full repository tree for detection. Tree walks are heavier than search-based discovery; omitting `paths[].organization` / `repos` scans broadly — scope with `organization` and `repos` when you can. If GitHub truncates the tree response, detection can be incomplete and webhook-driven deletes may be skipped (the exporter logs this). Configure `selector.paths` with organization (and optional repos), matching the file kind selector shape. Use `selector.providers` to limit which packaging formats to detect (defaults to all supported providers). | Provider | Paths detected | | ------------- | --------------------------------------------------------------- | | `claude` | `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` | | `cursor` | `.cursor-plugin/plugin.json` | | `codex` | `.codex-plugin/plugin.json` | | `agents` | `.agents/plugins/marketplace.json` | | `kimi` | `.kimi-plugin/plugin.json` | | `opencode` | files under `.opencode/plugins/` | | `pi` | files under `.pi/extensions/` | | `antigravity` | `gemini-extension.json` | ``` resources: - kind: plugin selector: query: 'true' providers: - claude - cursor - opencode paths: - organization: my-org # Optional: omit repos to scan all repositories in the organization repos: - name: my-plugin-repo branch: main port: entity: mappings: identifier: .__repository.full_name title: .plugin.displayName // .plugin.name blueprint: '"agentPlugin"' properties: description: .plugin.description version: .plugin.version url: .__repository.html_url repo: .__repository.full_name source: '"github"' supportsClaudeCode: .plugin.supports.claude supportsCursor: .plugin.supports.cursor supportsCodex: .plugin.supports.codex supportsAgents: .plugin.supports.agents supportsKimi: .plugin.supports.kimi supportsOpenCode: .plugin.supports.opencode supportsPi: .plugin.supports.pi supportsAntigravity: .plugin.supports.antigravity ``` **Available data example (click to expand)** ``` { "plugin": { "name": "superpowers", "displayName": "Superpowers", "description": "Core skills library", "version": "6.1.1", "supports": { "claude": true, "cursor": true, "codex": true, "agents": true, "kimi": true, "opencode": true, "pi": true, "antigravity": true }, "claude": { "name": "superpowers", "marketplaceName": "superpowers-dev" }, "cursor": { "name": "superpowers", "displayName": "Superpowers" }, "codex": { "name": "superpowers" }, "agents": { "name": "superpowers", "marketplaceName": "superpowers-marketplace" }, "kimi": { "name": "superpowers" }, "opencode": { "detected": true }, "pi": { "detected": true }, "antigravity": { "name": "superpowers" } }, "__repository": { "full_name": "my-org/superpowers", "name": "superpowers", "html_url": "https://github.com/my-org/superpowers" }, "__branch": "main", "__organization": "my-org" } ``` The raw payload always includes every provider key (`claude` … `antigravity`): a manifest object when present, `{ "detected": true }` for directory-only providers (`opencode` / `pi`), or `{}` when that provider is unsupported in the repo. Push webhooks follow the same branch rules as the file kind: when `paths[].repos` is omitted or `repos[].branch` is unset, only the repository default branch is processed. Feature-branch plugin manifest edits do not live-update the catalog. MCP servers are catalogued separately MCP servers shipped by a plugin (`mcp.json` or `.mcp.json`) are not part of the `plugin` kind payload. Catalog them with the [`mcp` kind](#ingest-mcp-servers). ## Ingest MCP servers[​](#ingest-mcp-servers "Direct link to Ingest MCP servers") Port can discover [MCP servers](https://cursor.com/docs/reference/plugins#mcp-servers) from `mcp.json` / `.mcp.json` files in your GitHub repositories using the dedicated `mcp` kind. Each uniquely named server listed under `mcpServers` becomes its own `mcpServer` catalog entity. Inventory, not a Port connector Discovered MCP servers use a dedicated blueprint so HTTP, stdio, `npx`, and Docker definitions can keep their own fields. They do not write to [`_mcp_server`](/context-lake/data-model/setup-blueprint/default-blueprints/.md#mcp-server) and do not become [MCP connectors](/agent-management/ai-registry/mcp-connectors/.md). `_mcp_server` stays dedicated to live connectors you register under **Connectors** (URL, OAuth, exposed tools). ### Create an MCP server blueprint[​](#create-an-mcp-server-blueprint "Direct link to Create an MCP server blueprint") Let's create an `mcpServer` blueprint before mapping. `url` is optional, so servers without an HTTP endpoint still ingest. `installation_args` stores the original `.mcp.config` payload. 1. Go to the [Data model](https://app.port.io/settings/data-model) page of your portal. 2. Click **+ Blueprint**. 3. Click `{...} Edit JSON`. 4. Copy and paste the following JSON configuration: **MCP server blueprint (click to expand)** Create in Port ``` { "identifier": "mcpServer", "title": "MCP Server", "icon": "AI", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "transport": { "title": "Transport", "type": "string", "enum": ["http", "stdio", "sse"], "enumColors": { "http": "blue", "stdio": "green", "sse": "purple" }, "description": "How the server is started or reached" }, "url": { "title": "URL", "type": "string", "format": "url", "description": "HTTP or SSE endpoint, when the server has one" }, "command": { "title": "Command", "type": "string", "description": "Executable used to start a stdio server, such as npx or docker" }, "args": { "title": "Args", "type": "array", "items": { "type": "string" } }, "headers": { "title": "Headers", "type": "object" }, "env": { "title": "Env", "type": "object" }, "installation_args": { "title": "Installation Args", "type": "object", "description": "Original install payload from mcp.json (command, args, env, and URL)" }, "path": { "title": "Path", "type": "string" }, "repo": { "title": "Repository", "type": "string" }, "repoUrl": { "title": "Repository URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false } } } ``` 5. Click **Save**. MCP discovery uses the same git-tree glob matching as the file and skill kinds. Tree walks are heavier than search-based discovery on large orgs. Omitting `organization` / `repos` on a path scans every accessible repository (with a classic PAT, that can span organizations). Scope with `organization` and `repos` when you can. Configure discovery with `selector.paths` (same multi-org shape as the file and skill kinds). Defaults cover the two conventional root-level locations: * `mcp.json`. * `.mcp.json`. Keep discovery at the repository root The defaults are exact, root-level paths rather than a recursive glob. Broadening a path to something like `**/mcp.json` also matches unrelated IDE-specific config such as `.cursor/mcp.json` or `.vscode/mcp.json`, which are not repository MCP manifests. **Example mapping (click to expand)** ``` resources: - kind: mcp selector: query: 'true' paths: - path: 'mcp.json' organization: my-org # Optional: omit repos to scan all repositories in the organization repos: - name: my-repo branch: main - path: '.mcp.json' organization: my-org repos: - name: my-repo branch: main port: entity: mappings: identifier: .mcp.name title: .mcp.name blueprint: '"mcpServer"' properties: description: .mcp.description transport: .mcp.transport url: .mcp.url command: .mcp.command args: .mcp.args headers: .mcp.headers env: .mcp.env installation_args: .mcp.config path: .mcp.path repo: .__repository.full_name repoUrl: .__repository.html_url relations: repository: .__repository.name ``` The identifier is the server name from `mcp.json`. If two repositories define the same name, later ingests overwrite the existing entity, so the catalog stays unique across the organization. `url` is mapped only when the payload has one. Stdio, `npx`, and Docker servers still ingest. `installation_args` keeps the original `.mcp.config` object. Test your mapping After adding the `mcp` kind and clicking **Resync**, open the mapping again to inspect live MCP server examples, then use **Test mapping** to validate your JQ. **Available data example (click to expand)** ``` { "mcp": { "name": "port", "transport": "http", "url": "https://mcp.port.io/v1", "description": "Port MCP server", "command": null, "args": [], "headers": { "x-read-only-mode": "0" }, "env": {}, "config": { "url": "https://mcp.port.io/v1", "headers": { "x-read-only-mode": "0" } }, "path": ".mcp.json", "blob_sha": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391" }, "__repository": { "full_name": "my-org/example-plugin", "name": "example-plugin", "html_url": "https://github.com/my-org/example-plugin", "default_branch": "main" }, "__branch": "main", "__organization": "my-org" } ``` Unlike the skill kind's one-file-to-one-entity model, a single matched file can fan out into several entities. Live push events reflect this correctly: adding a server, removing one server while the file stays (for example someone trims an entry out of `mcpServers`), and deleting the whole file are all detected and applied immediately, not only on the next full resync. Secrets are redacted by key, not by value `headers`, `env`, and the raw `config` passthrough are scanned recursively. Any value whose key looks like a credential (contains `token`, `secret`, `password`, `apiKey`, `auth`, or `credential`, case-insensitive) is replaced with `***REDACTED***` before it is cataloged. This is a heuristic, not a guarantee: a secret embedded directly in a CLI argument (for example `--token=abc123` inside `args`) or in a URL query parameter is not redacted, because there is no key to match against. Avoid putting credentials there, or keep repository access restricted accordingly. ### Limitations[​](#limitations-1 "Direct link to Limitations") * Files up to 1MB are supported, matching the file kind Contents API limit. * Live deletes need the previous file content. If it is unreachable (force-push, permissions, oversized or malformed JSON), the removal waits until the next full resync. Delayed deletions are never incorrect. * Renamed `mcp.json` / `.mcp.json` files are cleaned up on the next full resync, not live (same as the file and skill kinds). * Only the repository root is scanned by default. See the discovery caution above before using a glob. ## Ingest repositories via search API[​](#ingest-repositories-via-search-api "Direct link to Ingest repositories via search API") Port's Github integration allows you to ingest repositories using the [Github repository search API](https://docs.github.com/en/search-github/searching-on-github/searching-for-repositories). This feature provides granular control over ingested repositories, making the integration more capable and flexible. **Example mapping (click to expand)** ``` resources: - kind: repository selector: query: "true" repoSearch: query: "dev in:name archived:false" includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: description: if .description then .description else "" end visibility: if .private then "private" else "public" end defaultBranch: .default_branch readme: .__includedFiles["README.md"] url: .html_url language: if .language then .language else "" end - kind: pull-request selector: query: "true" repoSearch: query: "dev in:name archived:false" # repo search is also supported in pull requests. state: open port: entity: mappings: identifier: .head.repo.name + (.id|tostring) title: .title blueprint: '"githubPullRequest"' properties: creator: .user.login assignees: "[.assignees[].login]" reviewers: "[.requested_reviewers[].login]" status: .state closedAt: .closed_at updatedAt: .updated_at mergedAt: .merged_at createdAt: .created_at prNumber: .id link: .html_url relations: repository: .__repository ``` The repository search feature supports all resource kinds except `team`, `user`, `file`, `folder`, `skill`, `plugin`, and `mcp`. It helps you apply **granular filtering** so you can control which repositories are ingested. To learn how repository search works (including query syntax), see GitHub’s [repository search documentation](https://docs.github.com/en/search-github/searching-on-github/searching-for-repositories). ### Limitations[​](#limitations-2 "Direct link to Limitations") Read before using `repoSearch` This selector is **not recommended** when you need a complete, reliable catalog for more than 1,000 repositories. Review the limitations below before enabling it in your mapping. The repository search feature is subject to the limitations of the [GitHub Search API](https://docs.github.com/en/rest/search?apiVersion=2022-11-28#search-repositories): * **Hard cap of 1,000 repositories per query**: GitHub returns at most 1,000 matching repositories for any search query. Repositories beyond that cap are never ingested. * **Non-deterministic results above the cap**: When more than 1,000 repositories match your query, GitHub does not guarantee which 1,000 are returned. The ingested set can change between resyncs, and you cannot control which repositories are included. * **Not a workaround at scale**: Splitting work across multiple queries does not reliably cover large estates—each query still caps at 1,000 results, and matching sets can overlap or omit repositories unpredictably. * **Strict rate limits**: The Search API allows a maximum of 30 requests per minute. Large resyncs can be slow or fail with rate-limit errors. **Recommendation**: Omit `repoSearch` to ingest all repositories the integration can access (using the standard listing APIs). Use `repoSearch` only when you are confident fewer than 1,000 repositories match your query and you accept the GitHub Search API constraints above. ## Exclude archived repositories from discovery[​](#exclude-archived-repositories-from-discovery "Direct link to Exclude archived repositories from discovery") For repository-backed resource kinds, use the `excludeArchived` selector to skip archived repositories during repository discovery. This avoids fetching per-repository resources such as pull requests, issues, workflows, files, skills, plugins, and MCP servers from archived repositories. This is different from excluding archived repositories with a JQ mapping filter. A JQ filter runs after the integration has already fetched and processed the raw data. With `excludeArchived`, the integration can drop archived repositories from the repository batch before it fetches dependent per-repository resources, reducing follow-up GitHub API calls and resync time for those resource kinds. Unlike filtering with `repoSearch: { query: "archived:false" }`, `excludeArchived` does not rely on GitHub's Search API and is not subject to the Search API's 1,000-result cap. Enable `excludeArchived` on each resource kind that should skip archived repositories. **Example mapping (click to expand)** ``` resources: # Skip archived repositories before fetching pull requests. - kind: pull-request selector: query: "true" excludeArchived: true states: ["open"] port: entity: mappings: identifier: .head.repo.name + (.id|tostring) title: .title blueprint: '"githubPullRequest"' properties: link: .html_url # For file ingestion, set excludeArchived on each file pattern. - kind: file selector: query: "true" files: - path: "**/package.json" organization: my-org excludeArchived: true port: entity: mappings: identifier: .path title: .name blueprint: '"manifest"' properties: content: .content # For skill ingestion, set excludeArchived on each path entry. - kind: skill selector: query: "true" paths: - path: ".cursor/skills/**/SKILL.md" organization: my-org excludeArchived: true port: entity: mappings: identifier: .skill.path title: .skill.name blueprint: '"githubSkill"' properties: description: .skill.description # For plugin ingestion, set excludeArchived on each repository scope. - kind: plugin selector: query: "true" paths: - organization: my-org excludeArchived: true port: entity: mappings: identifier: .plugin.name title: .plugin.name blueprint: '"githubPlugin"' properties: repository: .__repository.name ``` Archived repository live events The `excludeArchived` selector is applied during repository discovery. If a repository is archived in GitHub after it has already been ingested, the live event for that archive action does not immediately delete all entities, dependencies, or relations that were previously created from that repository. Run a full resync to reconcile archived repositories with your `excludeArchived` selectors. If deletion is enabled for the affected resources, entities that are no longer returned by discovery can then be removed. ## Ingest the authenticated user’s personal account[​](#ingest-the-authenticated-users-personal-account "Direct link to Ingest the authenticated user’s personal account") You can optionally ingest the authenticated user’s personal account as a pseudo-organization. This allows you to sync personal repositories and dependent resource types as needed. ### Multi-organization setup[​](#multi-organization-setup "Direct link to Multi-organization setup") To include the authenticated user’s personal account in addition to regular organizations: * Set `includeAuthenticatedUser: true` in your configuration. * Run a resync to pull personal account data. ``` includeAuthenticatedUser: true #This is disabled by default. repositoryType: "all" resources: - kind: organization selector: query: "true" ``` ### Single-organization setup (personal account only)[​](#single-organization-setup-personal-account-only "Direct link to Single-organization setup (personal account only)") To sync only the authenticated user’s personal account when using single-organization authentication: * Set the GitHub organization value to the GitHub username of the authenticated user using the environment variable `OCEAN__INTEGRATION__CONFIG__GITHUB_ORGANIZATION=`. This will treat the personal GitHub account as the sole organization for ingestion. ### Limitations[​](#limitations-3 "Direct link to Limitations") * Webhooks are not supported for personal GitHub accounts. --- # Examples This page provides practical examples for mapping various GitHub resources to Port. ## Organizations and repositories[​](#organizations-and-repositories "Direct link to Organizations and repositories") Available from v3.0.0-beta The `organization` kind is available from version `v3.0.0-beta` onwards. The following example demonstrates how to ingest your GitHub organizations and their repositories to Port.
You can use the following Port blueprint definitions and `port-app-config.yml`: **Organization blueprint (click to expand)** Create in Port ``` { "identifier": "githubOrganization", "description": "This blueprint represents a service in our software catalog", "title": "Organization", "icon": "Github", "schema": { "properties": { "login": { "type": "string", "title": "Organization Login", "description": "The GitHub organization login name" }, "id": { "type": "number", "title": "Organization ID", "description": "GitHub organization ID" }, "nodeId": { "type": "string", "title": "Node ID", "description": "GitHub GraphQL node ID" }, "url": { "type": "string", "title": "API URL", "description": "GitHub API URL for the organization" }, "reposUrl": { "type": "string", "title": "Repositories URL", "description": "URL to organization's repositories" }, "eventsUrl": { "type": "string", "title": "Events URL", "description": "URL to organization's events" }, "hooksUrl": { "type": "string", "title": "Webhooks URL", "description": "URL to organization's webhooks" }, "issuesUrl": { "type": "string", "title": "Issues URL", "description": "URL to organization's issues" }, "membersUrl": { "type": "string", "title": "Members URL", "description": "URL to organization's members" }, "publicMembersUrl": { "type": "string", "title": "Public Members URL", "description": "URL to organization's public members" }, "avatarUrl": { "type": "string", "title": "Avatar URL", "description": "Organization avatar image URL" }, "description": { "type": "string", "title": "Description", "description": "Organization description" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Organization and repository mapping (click to expand)** ``` resources: - kind: organization selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: .login title: .name blueprint: '"githubOrganization"' properties: url: .html_url description: .description repositories: .public_repos createdAt: .created_at - kind: repository selector: query: "true" includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch relations: organization: .__organization.login ``` learn more * Port leverages the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitHub objects to Port Entities. * Click [Here](https://docs.github.com/en/rest/orgs#get-an-organization) for the GitHub organization object structure. * Click [Here](https://docs.github.com/en/rest/repos#get-a-repository) for the GitHub repository object structure. After creating the blueprints and committing the `port-app-config.yml` file, you will see new entities in Port matching your organizations and their repositories. The repositories will have a relation to their parent organization. ## Pull requests, issues, and branches[​](#pull-requests-issues-and-branches "Direct link to Pull requests, issues, and branches") The following examples demonstrate how to ingest GitHub repositories together with pull requests, issues, branches, and branch metadata. ### Repositories and pull requests[​](#repositories-and-pull-requests "Direct link to Repositories and pull requests") The following example demonstrates how to ingest your GitHub repositories, their README.md file contents and open pull requests to Port.
You can use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Pull request blueprint (click to expand)** Create in Port ``` { "identifier": "githubPullRequest", "title": "Pull Request", "icon": "Github", "schema": { "properties": { "creator": { "title": "Creator", "type": "string" }, "assignees": { "title": "Assignees", "type": "array" }, "reviewers": { "title": "Reviewers", "type": "array" }, "status": { "title": "Status", "type": "string", "enum": ["merged", "open", "closed"], "enumColors": { "merged": "purple", "open": "green", "closed": "red" } }, "closedAt": { "title": "Closed At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "mergedAt": { "title": "Merged At", "type": "string", "format": "date-time" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "link": { "format": "url", "type": "string" }, "leadTimeHours": { "title": "Lead Time in hours", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "days_old": { "title": "Days Old", "icon": "DefaultProperty", "calculation": "(now / 86400) - (.properties.createdAt | capture(\"(?\\\\d{4}-\\\\d{2}-\\\\d{2})\") | .date | strptime(\"%Y-%m-%d\") | mktime / 86400) | floor", "type": "number" } }, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false } } } ``` **Repository and pull request mapping (click to expand)** ``` resources: - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md port: entity: mappings: identifier: ".name" # The Entity identifier will be the repository name. title: ".name" blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] # fetching the README.md file that is within the root folder of the repository and ingesting its contents as a markdown property url: .html_url defaultBranch: .default_branch relations: organization: .__organization.login - kind: pull-request selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. states: ["open"] # List of Pull Request states to include: use ["open"], ["closed"], or ["open","closed"]. 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 | gsub("\\["; "-") | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")) assignees: '[.assignees[].login | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")]' reviewers: '[.requested_reviewers[].login | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")]' status: ".state" # merged, closed, opened closedAt: ".closed_at" updatedAt: ".updated_at" mergedAt: ".merged_at" createdAt: ".created_at" prNumber: ".number" link: ".url" leadTimeHours: >- (.created_at as $createdAt | .merged_at as $mergedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($mergedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $mergedTimestamp | if $mergedTimestamp == null then null else (((($mergedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) end) relations: repository: .__repository ``` learn more * Port leverages the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitHub objects to Port Entities. * Click [Here](https://docs.github.com/en/rest/repos#get-a-repository) for the GitHub repository object structure. * Click [Here](https://docs.github.com/en/rest/pulls#get-a-pull-request) for the GitHub pull request object structure. After creating the blueprints and committing the `port-app-config.yml` file to your `.github-private` repository (for global configuration), or to any specific repositories (for per-repo configuration), you will see new entities in Port matching your repositories alongside their README.md file contents and pull requests. (Remember that the `port-app-config.yml` file has to be in the **default branch** of the repository to take effect). Additionally, you can configure your selector to limit the number of closed pull requests to ingest using a combination of `maxResults` and `since`. By default, we only fetch 100 closed pull requests within 60 days. **Closed pull request selector (click to expand)** ``` - kind: pull-request selector: query: "true" states: ["closed"] # Specifically for closed PRs. maxResults: 50 # Limit closed PRs to 50. Please note that this flag may cause rate limits. since: 60 # Fetch closed PRs within 60 days. Please note that this flag may cause rate limits. ``` Pull request performance impact To prevent rate limits and/or performance degradation, if you intend to ingest closed pull requests, you must add a filtering criteria such as `since` and/or `maxResults` to limit the number of items ingested. The example above demonstrates this by retrieving 50 results from pull requests closed within the last 60 days. You can also choose which GitHub API to use for pull requests. By default, the integration uses the REST API, but you can switch to GraphQL by adding an `api` selector: **GraphQL pull request mapping (click to expand)** ``` - kind: pull-request selector: query: "true" states: ["open"] api: "graphql" # Use the GraphQL API instead of REST. port: entity: mappings: identifier: .__repository + (.fullDatabaseId|tostring) title: .title blueprint: '"githubPullRequest"' properties: creator: (.author.login | gsub("\\["; "-") | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")) assignees: '[.assignees[].login | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")]' reviewers: '[.requested_reviewers[].login | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")]' status: .state createdAt: .createdAt updatedAt: .updatedAt mergedAt: .mergedAt prNumber: ".number" link: .url relations: repository: .__repository ``` When you use `api: "rest"`, the pull request data follows the REST API shape (for example `.user.login`, `.created_at`, `.updated_at`, `.merged_at`, `.html_url`, `.additions`, `.deletions`, `.changed_files`).
When you use `api: "graphql"`, the pull request data follows the GraphQL schema and uses different key names (for example `.author.login`, `.createdAt`, `.updatedAt`, `.mergedAt`, `.url`, `.number`, `.additions`, `.deletions`, `.changedFiles`) and also adds convenience fields such as `assignees`, `requested_reviewers`, `comments`, `review_comments`, `commits`, `mergeable_state` and `mergeable`. Make sure you update your jq mappings to use the correct keys for the API you choose. #### First commit on the pull request branch[​](#first-commit-on-the-pull-request-branch "Direct link to First commit on the pull request branch") When you set `api` to `graphql`, you can optionally set `enrichWithFirstCommit` to `true` on the `pull-request` selector. The integration then adds a `firstCommit` object on each pull request payload with the earliest commit on the branch returned by GitHub (`oid` and `committedDate` in UTC). You can map those fields in Port (for example when you model [DORA Lead Time for Changes](/solutions/engineering-intelligence/measure-and-track-standards/dora-metrics/.md)) or related workflows. **First commit enrichment mapping (click to expand)** ``` - kind: pull-request selector: query: "true" states: ["open"] api: "graphql" enrichWithFirstCommit: true port: entity: mappings: identifier: .__repository + (.fullDatabaseId|tostring) title: .title blueprint: '"githubPullRequest"' properties: firstCommitOid: .firstCommit.oid firstCommitAt: .firstCommit.committedDate ``` GraphQL only `enrichWithFirstCommit` is only valid when `api` is `graphql`. If you enable it while using the REST API, the integration configuration is rejected with a validation error. #### Reduce rate limit cost by excluding graphql fields[​](#reduce-rate-limit-cost-by-excluding-graphql-fields "Direct link to Reduce rate limit cost by excluding graphql fields") When using the GraphQL API for pull requests, you may want to exclude irrelevant or non-beneficial fields (for example `additions`, `deletions`, `changedFiles`) from the pull request query using `excludeGraphqlFields`.
This can also help: * Reduce unnecessary GraphQL rate limit costs. * Avoid GitHub server issues caused by problematic fields. **GraphQL field exclusions (click to expand)** ``` - kind: pull-request selector: query: "true" states: ["open"] api: "graphql" excludeGraphqlFields: - additions - deletions - changedFiles ``` GraphQL only `excludeGraphqlFields` is only relevant when the pull request selector `api` is set to `graphql`. It applies **only to top-level pull request fields** (fields directly on the `PullRequest` object) and cannot be used to exclude nested fields. ### Repositories and issues[​](#repositories-and-issues "Direct link to Repositories and issues") The following example demonstrates how to ingest your GitHub repositories and their issues to Port, you may use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Issue blueprint (click to expand)** Create in Port ``` { "identifier": "githubIssue", "title": "Issue", "icon": "Github", "schema": { "properties": { "creator": { "title": "Creator", "type": "string" }, "assignees": { "title": "Assignees", "type": "array" }, "labels": { "title": "Labels", "type": "array" }, "status": { "title": "Status", "type": "string", "enum": ["open", "closed"], "enumColors": { "open": "green", "closed": "purple" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "closedAt": { "title": "Closed At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "description": { "title": "Description", "type": "string", "format": "markdown" }, "issueNumber": { "title": "Issue Number", "type": "number" }, "link": { "title": "Link", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "target": "githubRepository", "required": true, "many": false } } } ``` **Repository and issue mapping (click to expand)** ``` resources: - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md port: entity: mappings: identifier: ".name" # The Entity identifier will be the repository name. title: ".name" blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: issue selector: query: ".pull_request == null" # JQ boolean query. If evaluated to false - skip syncing the object. state: "closed" 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" ``` * Labels You can use the `labels` selector to filter issues by labels.
An issue is ingested only if it has **all** of the labels you set (AND logic); if it has only some of them, it is not included. ``` - kind: issue selector: query: ".pull_request == null" labels: ["bug", "high-priority"] ``` learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/#setup) section to learn more about the `port-app-config.yml` setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitHub objects to Port Entities. * Click [Here](https://docs.github.com/en/rest/repos#get-a-repository) for the GitHub repository object structure. * Click [Here](https://docs.github.com/en/rest/issues#get-an-issue) for the GitHub issue object structure. ### Repositories and branches[​](#repositories-and-branches "Direct link to Repositories and branches") The following example demonstrates how to ingest your GitHub repositories and their branches to Port, you may use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Branch blueprint (click to expand)** Create in Port ``` { "identifier": "githubBranch", "title": "Branch", "icon": "Github", "schema": { "properties": { "is_protected": { "title": "Is branch protected", "type": "boolean", "icon": "DefaultProperty" }, "commit_sha": { "title": "Commit SHA", "type": "string", "icon": "DefaultProperty" }, "commit_url": { "title": "Commit URL", "type": "string", "format": "url" }, "is_default_branch": { "title": "Is default branch", "type": "boolean", "icon" : "DefaultProperty" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": false } } } ``` **Repository and branch mapping (click to expand)** ``` resources: - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: branch selector: query: 'true' port: entity: mappings: identifier: .__repository + "_" + .name title: .__repository + " " + .name blueprint: '"githubBranch"' properties: is_protected: .protected commit_sha: .commit.sha commit_url: .commit.url is_default_branch: .__is_default_branch relations: repository: .__repository ``` * detailed * protectionRules * branchNames * defaultBranchOnly Set `detailed: true` to include the latest commit details for each branch. By default it's `false`, which returns a smaller branch payload. Enable it only if you need commit-level fields. ``` - kind: branch selector: query: "true" detailed: true ``` Set `protectionRules: true` to include branch protection rules in each branch payload. By default it's `false`. ``` - kind: branch selector: query: "true" protectionRules: true ``` Use `branchNames` to ingest only specific branches. When you set it, the integration fetches those branches explicitly instead of listing all branches using pagination. ``` - kind: branch selector: query: "true" branchNames: ["main", "develop"] ``` Use `defaultBranchOnly` to ingest only the repository's default branch. When you set it, it takes precedence and `branchNames` will be ignored. ``` - kind: branch selector: query: "true" defaultBranchOnly: true ``` ### Repositories and last contributor[​](#repositories-and-last-contributor "Direct link to Repositories and last contributor") The following example demonstrates how to ingest your GitHub repositories and their last contributor to Port, you may use the following Port blueprint definitions and `port-app-config.yml`: **Branch blueprint (click to expand)** Create in Port ``` { "identifier": "githubBranch", "title": "Github Branch", "icon": "Microservice", "schema": { "properties": { "is_protected": { "title": "Is branch protected", "type": "boolean", "icon": "DefaultProperty" }, "commit_sha": { "title": "Commit SHA", "type": "string", "icon": "DefaultProperty" }, "commit_url": { "title": "Commit URL", "type": "string", "format": "url" }, "last_contributor": { "title": "Last contributor", "icon": "TwoUsers", "type": "string", "format": "user" }, "last_push": { "title": "Last push", "icon": "GitPullRequest", "description": "Last commit to the main branch", "type": "string", "format": "date-time" }, "is_default_branch": { "title": "Is default branch", "type": "boolean", "icon": "DefaultProperty" }, }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" }, "last_contributor": { "title": "Last contributor", "icon": "TwoUsers", "type": "string", "format": "user" }, "last_push": { "icon": "GitPullRequest", "title": "Last push", "description": "Last commit to the main branch", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "github_branch": { "title": "Github Branch", "target": "githubBranch", "required": false, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: branch selector: query: .__is_default_branch detailed: true # Return full branch data, including the commit detailed data port: entity: mappings: identifier: .__repository_object.name + "-" + .name blueprint: '"githubBranch"' properties: last_contributor: .commit.commit.author.email | ascii_downcase last_push: .commit.commit.committer.date - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url language: .language relations: github_branch: .name + "-" + .default_branch ``` The last contributor is the author of the last commit in the default branch of the repository.
This example uses the `branch` kind with `detailed: true` to fetch the latest commit data and mirrors the last contributor and last commit date back onto the repository entity. By default, `detailed` is set to `false`, which returns a lighter branch payload. It should only be set to `true` when you need access to commit-level fields. Reduce API usage (branch ingestion scoping) You can reduce API usage by scoping branch ingestion using one of the following selector options: * `defaultBranchOnly: true`: Optional (Recommended). Sync only the repository's default branch. When set, it takes precedence and `branchNames` will be ignored. * `branchNames`: Optional. Use to ingest only specific branches by name (for example, only the default branch). When set, the integration fetches those branches explicitly instead of listing all branches using pagination. ### Repositories and branch protection rules[​](#repositories-and-branch-protection-rules "Direct link to Repositories and branch protection rules") The following example demonstrates how to ingest your GitHub repositories and their branch protection rules to Port, you may use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Branch protection blueprint (click to expand)** Create in Port ``` { "identifier": "githubBranchProtection", "title": "Branch protection", "icon": "Github", "schema": { "properties": { "require_approval_count": { "title": "Require approvals", "type": "number", "icon": "DefaultProperty", "description": "The number of approvals required before merging a pull request." }, "require_code_owner_review": { "title": "Require code owner review", "type": "boolean", "icon": "DefaultProperty", "description": "Requires review from code owners before a pull request can be merged." }, "allow_deletions": { "title": "Allow deletions", "type": "boolean", "icon": "DefaultProperty", "description": "Allows users with bypass permissions to delete matching references in the branch." }, "allow_force_pushes": { "title": "Allow force pushes", "type": "boolean", "icon": "DefaultProperty", "description": "Permits users with push access to force push changes to the branch." }, "require_signed_commits": { "title": "Require signed commits", "type": "boolean", "icon": "DefaultProperty", "description": "Ensures that commits pushed to the branch are signed." }, "require_linear_history": { "title": "Require linear history", "type": "boolean", "icon": "DefaultProperty", "description": "Enforces a linear history in the branch by preventing merge commits." }, "restrict_creations": { "title": "Restrict creations", "type": "boolean", "icon": "DefaultProperty", "description": "Restricts the creation of matching references in the branch, allowing only users with bypass permissions." }, "restrict_updates": { "title": "Restrict updates", "type": "boolean", "icon": "DefaultProperty", "description": "Prevents updates to matching references in the branch, limiting changes to users with bypass permissions." }, "require_conversation_resolution": { "title": "Require resolution", "type": "boolean", "icon": "DefaultProperty", "description": "Ensures that all comments and conversations are resolved before merging a pull request." }, "lock_branch": { "title": "Lock branch", "type": "boolean", "icon": "DefaultProperty", "description": "Locks the branch, preventing any changes from being made unless explicitly unlocked." }, "block_force_pushes": { "title": "Block force pushes", "type": "boolean", "icon": "DefaultProperty", "description": "Prevents users with push access from force pushing to refs." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: branch selector: query: "true" branchNames: ["main", "develop"] # Alternatively, sync only the repository's default branch (takes precedence over branchNames): # defaultBranchOnly: true protectionRules: true port: entity: mappings: identifier: .__repository + "_" + .name title: .__repository + " " + .name blueprint: '"githubBranchProtection"' properties: require_approval_count: >- .__protection_rules.required_pull_request_reviews.required_approving_review_count require_code_owner_review: >- .__protection_rules.required_pull_request_reviews.require_code_owner_reviews allow_force_pushes: .__protection_rules.allow_force_pushes.enabled allow_deletions: .__protection_rules.allow_deletions.enabled require_signed_commits: .__protection_rules.required_signatures.enabled require_linear_history: .__protection_rules.required_linear_history.enabled restrict_creations: .__protection_rules.block_creations.enabled restrict_updates: .__protection_rules.restrict_updates.enabled require_conversation_resolution: >- .__protection_rules.required_conversation_resolution.enabled lock_branch: .__protection_rules.lock_branch.enabled block_force_pushes: .__protection_rules.allow_force_pushes.enabled == false relations: repository: .__repository ``` To enable protection rules ingestion, set: `protectionRules: true`: Required. Enables fetching branch protection rules, which are needed for the `branchProtection` mapping. If your integration user lacks branch protection permissions, the field `.__protection_rules` will be empty. Reduce API usage (branch ingestion scoping) You can reduce API usage by scoping branch ingestion using one of the following selector options: * `defaultBranchOnly: true`: Optional (Recommended). Sync only the repository's default branch. When set, it takes precedence and `branchNames` will be ignored. * `branchNames`: Optional. Use to ingest only specific branches by name (for example, only the default branch). When set, the integration fetches those branches explicitly instead of listing all branches using pagination. ## Files and file contents[​](#files-and-file-contents "Direct link to Files and file contents") The following examples demonstrate how to ingest repository files and file contents into Port. ### Files and file contents[​](#files-and-file-contents-1 "Direct link to Files and file contents") The following example demonstrates ingestion of dependencies from a `package.json` file in your repository into Port: **Package blueprint (click to expand)** Create in Port ``` { "identifier": "package", "title": "Package", "icon": "Package", "schema": { "properties": { "package": { "icon": "DefaultProperty", "type": "string", "title": "Package" }, "version": { "icon": "DefaultProperty", "type": "string", "title": "Version" } }, "required": [ "package", "version" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Port config YAML (click to expand)** ``` - kind: file selector: query: 'true' files: - path: '**/package.json' organization: my-org # Optional if githubOrganization is set (required if not set) repos: - name: vscode branch: main port: itemsToParse: .content.dependencies | to_entries entity: mappings: identifier: >- .item.key + "_" + if (.item.value | startswith("^")) then .item.value[1:] else .item.value end title: .item.key + "@" + .item.value blueprint: '"package"' properties: package: .item.key version: .item.value ``` The example will parse the `package.json` file in your repository and extract the dependencies into Port entities.
For more information about ingesting files and file contents, click [here](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md#ingest-files-from-your-repositories). ### Files and repositories[​](#files-and-repositories "Direct link to Files and repositories") The following example demonstrates mapping files to repository. **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **File blueprint (click to expand)** ``` { "identifier": "files", "title": "Files", "icon": "Github", "schema": { "properties": { "name": { "type": "string", "title": "Name" }, "path": { "type": "string", "title": "Path" } }, "required": [ "name", "path" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "repository", "target": "githubRepository", "required": false, "many": false } } } ``` **File and repository mapping (click to expand)** ``` deleteDependentEntities: true resources: - kind: repository selector: query: 'true' includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: description: if .description then .description else "" end visibility: if .private then "private" else "public" end defaultBranch: .default_branch readme: .__includedFiles["README.md"] url: .html_url language: if .language then .language else "" end - kind: file selector: query: 'true' files: - path: 'README.md' organization: my-org # Optional if githubOrganization is set (required if not set) repos: # Optional: list repositories to include (omit to scan all repos) - name: test-repo branch: main port: entity: mappings: identifier: .name title: .name blueprint: '"files"' properties: name: .name path: .path relations: repository: .repository.name ``` ## Agent skills and plugins[​](#agent-skills-and-plugins "Direct link to Agent skills and plugins") The following examples demonstrate how to ingest agent skills and plugin packages from your repositories into Port. ### Agent skills[​](#agent-skills "Direct link to Agent skills") The following example ingests Agent Skills (`SKILL.md`) using the native `skill` kind: **Skill mapping (click to expand)** ``` resources: - kind: skill selector: query: 'true' paths: - path: '.cursor/skills/**/SKILL.md' organization: my-org repos: - name: my-skills-repo branch: main - path: 'skills/**/SKILL.md' organization: my-org port: entity: mappings: identifier: .__repository.full_name + "/" + .skill.skillMdPath title: .skill.name // .skill.skillMdPath blueprint: '"skill"' properties: description: .skill.description instructions: .skill.instructions path: .skill.skillMdPath root: .skill.root repo: .__repository.full_name repoUrl: .__repository.html_url branch: .__branch source: '"github"' ``` For more information, see [Ingest agent skills](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-agent-skills). ### Agent plugins[​](#agent-plugins "Direct link to Agent plugins") The following example detects agent plugin packages and creates one entity per repository: **Plugin mapping (click to expand)** ``` resources: - kind: plugin selector: query: 'true' providers: - claude - cursor - opencode paths: - organization: my-org repos: - name: my-plugin-repo branch: main port: entity: mappings: identifier: .__repository.full_name title: .plugin.displayName // .plugin.name blueprint: '"agentPlugin"' properties: description: .plugin.description version: .plugin.version url: .__repository.html_url repo: .__repository.full_name source: '"github"' # Map every supports* flag you care about (capabilities maps all eight). supportsClaudeCode: .plugin.supports.claude supportsCursor: .plugin.supports.cursor supportsCodex: .plugin.supports.codex supportsAgents: .plugin.supports.agents supportsKimi: .plugin.supports.kimi supportsOpenCode: .plugin.supports.opencode supportsPi: .plugin.supports.pi supportsAntigravity: .plugin.supports.antigravity ``` For more information, see [Ingest agent plugins](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-agent-plugins). ## MCP servers[​](#mcp-servers "Direct link to MCP servers") The following example catalogs unique MCP servers from `mcp.json` / `.mcp.json` files into a dedicated `mcpServer` blueprint. This inventory is separate from `_mcp_server` connectors. **MCP server blueprint (click to expand)** Create in Port ``` { "identifier": "mcpServer", "title": "MCP Server", "icon": "AI", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "transport": { "title": "Transport", "type": "string", "enum": ["http", "stdio", "sse"], "enumColors": { "http": "blue", "stdio": "green", "sse": "purple" }, "description": "How the server is started or reached" }, "url": { "title": "URL", "type": "string", "format": "url", "description": "HTTP or SSE endpoint, when the server has one" }, "command": { "title": "Command", "type": "string", "description": "Executable used to start a stdio server, such as npx or docker" }, "args": { "title": "Args", "type": "array", "items": { "type": "string" } }, "headers": { "title": "Headers", "type": "object" }, "env": { "title": "Env", "type": "object" }, "installation_args": { "title": "Installation Args", "type": "object", "description": "Original install payload from mcp.json (command, args, env, and URL)" }, "path": { "title": "Path", "type": "string" }, "repo": { "title": "Repository", "type": "string" }, "repoUrl": { "title": "Repository URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false } } } ``` **MCP mapping (click to expand)** ``` resources: - kind: mcp selector: query: 'true' paths: - path: 'mcp.json' organization: my-org repos: - name: my-repo branch: main - path: '.mcp.json' organization: my-org repos: - name: my-repo branch: main port: entity: mappings: identifier: .mcp.name title: .mcp.name blueprint: '"mcpServer"' properties: description: .mcp.description transport: .mcp.transport url: .mcp.url command: .mcp.command args: .mcp.args headers: .mcp.headers env: .mcp.env installation_args: .mcp.config path: .mcp.path repo: .__repository.full_name repoUrl: .__repository.html_url relations: repository: .__repository.name ``` For more information, see [Ingest MCP servers](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#ingest-mcp-servers). ## Monorepos and workflows[​](#monorepos-and-workflows "Direct link to Monorepos and workflows") The following examples demonstrate how to ingest monorepo folders, GitHub Actions workflows, and workflow runs into Port. ### Repositories and monorepos[​](#repositories-and-monorepos "Direct link to Repositories and monorepos") The following example demonstrates how to ingest your GitHub repositories and their folders to Port. By following this example you can map your different services, packages and libraries from your monorepo into separate entities in Port. You may use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` This example shows **Option A** - ingesting a `README.md` file relative to each matched folder (for example `apps/service-a/README.md`). If you want the repository root `README.md` attached to every folder entity, see **Option B** below. **Option A: Folder README (relative to each folder) (click to expand)** ``` resources: - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: apps/* # Relative path to the folders within the repositories. organization: my-org # Optional if githubOrganization is set (required if not set) includedFiles: # These paths are resolved relative to each matched folder (e.g. apps/service-a/README.md) - README.md repos: # List of repositories to include folders from. - name: backend-service branch: main - name: frontend-service # if you don't specify a branch, we'll use the repository's default branch port: entity: mappings: identifier: .folder.path | split("/") | last title: .folder.path | split("/") | last blueprint: '"githubRepository"' properties: url: .__repository.html_url + "/tree/" + .__repository.default_branch + "/" + .folder.path readme: .folder.__includedFiles["README.md"] ``` **Option B: Repository root README (same README for all folders) (click to expand)** ``` resources: - kind: folder selector: query: "true" folders: - path: apps/* organization: my-org repos: - name: backend-service branch: main - name: frontend-service includedFiles: # These paths are fetched from the repository root and attached under .__includedFiles - README.md port: entity: mappings: identifier: .folder.path | split("/") | last title: .folder.path | split("/") | last blueprint: '"githubRepository"' properties: url: .__repository.html_url + "/tree/" + .__repository.default_branch + "/" + .folder.path readme: .__includedFiles["README.md"] ``` To retrieve the root folders of your monorepo, use the following syntax in your `port-app-config.yml`: Organization and repository filtering for folders Both `organization` and `repos` under `folders` are optional. You can: * Specify only `organization`: scan all repositories in that organization for matching folders. * Specify only `repos`: scan only those repositories across accessible organizations. * Omit both: scan all accessible repositories for matching folders. Use `path` and `repositoryType` to scope results and improve performance. **Monorepo folder selector (click to expand)** ``` - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: "*" # Relative path to the folders within the repositories. repos: # Optional: list repositories to include (omit to scan all repos) - name: backend-service - name: frontend-service ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/#setup) section to learn more about the `port-app-config.yml` setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitHub objects to Port Entities. * Click [Here](https://docs.github.com/en/rest/repos#get-a-repository) for the GitHub repository object structure. * Click [Here](https://docs.github.com/en/rest/git/trees#get-a-tree) for the GitHub folder object structure.
If you encounter 403 errors when syncing the `folder` kind, verify that your GitHub App or token has the **Contents** permission with **Read** access. The `folder` kind requires access to repository tree data via the GitHub Trees API. If permission issues persist, consider using the `file` kind as an alternative for ingesting specific files from your repositories. ### Repositories, workflows and workflow runs[​](#repositories-workflows-and-workflow-runs "Direct link to Repositories, workflows and workflow runs") The following example demonstrates how to ingest your GitHub repositories, their workflows and workflow runs to Port, you may use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Workflow blueprint (click to expand)** Create in Port ``` { "identifier": "githubWorkflow", "title": "Workflow", "icon": "Github", "schema": { "properties": { "path": { "title": "Path", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": [ "active", "deleted", "disabled_fork", "disabled_inactivity", "disabled_manually" ], "enumColors": { "active": "green", "deleted": "red" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "deletedAt": { "title": "Deleted At", "type": "string", "format": "date-time" }, "link": { "title": "Link", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false } } } ``` **Workflow run blueprint (click to expand)** Create in Port ``` { "identifier": "githubWorkflowRun", "title": "Workflow Run", "icon": "Github", "schema": { "properties": { "name": { "title": "Name", "type": "string" }, "triggeringActor": { "title": "Triggering Actor", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": [ "completed", "action_required", "cancelled", "startup_failure", "failure", "neutral", "skipped", "stale", "success", "timed_out", "in_progress", "queued", "requested", "waiting" ], "enumColors": { "queued": "yellow", "in_progress": "yellow", "success": "green", "failure": "red" } }, "conclusion": { "title": "Conclusion", "type": "string", "enum": [ "completed", "action_required", "cancelled", "startup_failure", "failure", "neutral", "skipped", "stale", "success", "timed_out", "in_progress", "queued", "requested", "waiting" ], "enumColors": { "queued": "yellow", "in_progress": "yellow", "success": "green", "failure": "red" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "runStartedAt": { "title": "Run Started At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "runNumber": { "title": "Run Number", "type": "number" }, "runAttempt": { "title": "Run Attempts", "type": "number" }, "link": { "title": "Link", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "workflow": { "target": "githubWorkflow", "required": true, "many": false } } } ``` **Repository, workflow, and workflow run mapping (click to expand)** ``` resources: - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md port: entity: mappings: identifier: ".name" # The Entity identifier will be the repository name. title: ".name" blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: workflow selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: ".__repository + (.id|tostring)" title: ".name" blueprint: '"githubWorkflow"' properties: path: ".path" status: ".state" createdAt: ".created_at" updatedAt: ".updated_at" link: ".html_url" relations: repository: ".__repository" - kind: workflow-run selector: query: .status != "completed" # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: identifier: ".repository.name + (.id|tostring)" title: ".display_title" blueprint: '"githubWorkflowRun"' properties: name: ".name" triggeringActor: ".triggering_actor.login" status: ".status" conclusion: ".conclusion" createdAt: ".created_at" runStartedAt: ".run_started_at" updatedAt: ".updated_at" deletedAt: ".deleted_at" runNumber: ".run_number" runAttempt: ".run_attempt" link: ".html_url" relations: workflow: ".repository.name + (.workflow_id|tostring)" ``` learn more * Click [Here](https://docs.github.com/en/rest/actions/workflows#get-a-workflow) for the GitHub workflow object structure. * Click [Here](https://docs.github.com/en/rest/actions/workflow-runs#get-a-workflow-run) for the GitHub workflow run object structure. ## Teams and access control[​](#teams-and-access-control "Direct link to Teams and access control") The following examples demonstrate how to ingest teams, users, collaborators, and repository access relationships into Port. ### Repositories and teams[​](#repositories-and-teams "Direct link to Repositories and teams") The following example demonstrates how to ingest your GitHub repositories and their teams to Port.
You can use the following Port blueprint definitions and `port-app-config.yml`: **Team blueprint (click to expand)** Create in Port ``` { "identifier": "githubTeam", "title": "GitHub Team", "icon": "Github", "schema": { "properties": { "slug": { "title": "Slug", "type": "string" }, "description": { "title": "Description", "type": "string" }, "link": { "title": "Link", "icon": "Link", "type": "string", "format": "url" }, "permission": { "title": "Permission", "type": "string" }, "notification_setting": { "title": "Notification Setting", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "githubTeams": { "title": "GitHub Teams", "target": "githubTeam", "required": false, "many": true } } } ``` **Port port-app-config.yml (click to expand)** ``` createMissingRelatedEntities: true resources: - kind: team selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. members: false port: entity: mappings: identifier: ".id | tostring" title: .name blueprint: '"githubTeam"' properties: name: .name slug: .slug description: .description link: .html_url permission: .permission notification_setting: .notification_setting - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedRelations: teams: true includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch relations: githubTeams: "[.__teams[].id | tostring]" ``` ### Repositories with multiple relationships[​](#repositories-with-multiple-relationships "Direct link to Repositories with multiple relationships") You can now include multiple relationship types in a single repository configuration. The repository selector supports: * `includedRelations`: a structured configuration that also allows per-relationship options (recommended when you need configuration such as collaborator `affiliation`). **Repository relationship mappings (click to expand)** * Teams * Collaborators * SBOM * Custom properties * Pages * Multiple relations This ingests the [teams with access to the repository](https://docs.github.com/en/rest/repos#list-repository-teams). ``` - kind: repository selector: query: "true" includedRelations: teams: true ``` This ingests the [collaborators of the repository](https://docs.github.com/en/rest/collaborators#list-repository-collaborators). ``` - kind: repository selector: query: "true" includedRelations: collaborators: affiliation: "all" # "all" | "direct" | "outside" ``` This ingests the [Software Bill of Materials (SBOM)](https://docs.github.com/en/rest/dependency-graph/sboms#get-a-software-bill-of-materials-sbom-for-a-repository) for the repository. ``` - kind: repository selector: query: "true" includedRelations: sbom: true ``` This ingests the [organization custom properties](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization) for each repository. See the [full example](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/examples/.md#repositories-and-custom-properties) for blueprint and mapping details. ``` - kind: repository selector: query: "true" includedRelations: customProperties: true ``` This ingests the [GitHub Pages configuration](https://docs.github.com/en/rest/pages#get-a-github-pages-site) for the repository. ``` - kind: repository selector: query: "true" includedRelations: pages: true ``` ``` - kind: repository selector: query: "true" includedRelations: teams: true sbom: true customProperties: true pages: true collaborators: affiliation: "all" # "all" | "direct" | "outside" includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch custom_properties: .custom_properties pages: .__pages relations: githubTeams: "[.__teams[].id | tostring]" githubCollaborators: "[.__collaborators[].login]" ``` Performance consideration While you can include multiple relationship types in a single configuration, this may impact resync performance for large repositories. For optimal performance, consider separating into multiple repository blocks: **Separate repository relation mappings (click to expand)** ``` # Separate blocks for better performance - kind: repository selector: query: "true" includedRelations: teams: true # ... rest of configuration - kind: repository selector: query: "true" includedRelations: collaborators: affiliation: "all" # "all" | "direct" | "outside" # ... rest of configuration ``` ### Teams and users[​](#teams-and-users "Direct link to Teams and users") The following shows how we can map teams and their members (as `githubUser` entities) using the `members: true` selector. When `members: true`, teams are fetched using the GitHub GraphQL API, where `id` is a global node ID and `databaseId` is the numeric ID that matches the team `id` returned by the GitHub REST API. Enterprise teams Members of GitHub Enterprise teams (teams whose slugs are prefixed with `ent:`) are ingested the same way as standard organization teams, with no additional configuration. Requires GitHub integration version `6.2.5` or later. **GitHub User blueprint (click to expand)** Create in Port ``` { "identifier": "githubUser", "title": "GitHub User", "icon": "Github", "schema": { "properties": { "name": { "type": "string", "title": "Name" }, "email": { "type": "string", "title": "Email" }, "samlEmail": { "type": "string", "title": "SAML Email" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Team blueprint (click to expand)** Create in Port ``` { "identifier": "githubTeam", "title": "GitHub Team", "icon": "Github", "schema": { "properties": { "slug": { "title": "Slug", "type": "string" }, "description": { "title": "Description", "type": "string" }, "link": { "title": "Link", "icon": "Link", "type": "string", "format": "url" }, "permission": { "title": "Permission", "type": "string" }, "notification_setting": { "title": "Notification Setting", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "members": { "title": "Members", "target": "githubUser", "required": false, "many": true } } } ``` **Team and member mapping (click to expand)** ``` createMissingRelatedEntities: true resources: - kind: user selector: query: 'true' port: entity: mappings: identifier: .login title: .login blueprint: '"githubUser"' properties: name: .name email: .email - kind: team selector: query: 'true' members: true port: entity: mappings: identifier: .databaseId | tostring title: .name blueprint: '"githubTeam"' properties: slug: .slug description: .description link: .url permission: .permission notification_setting: .notificationSetting relations: members: '[.members.nodes[].login]' ``` #### Enrich payload with SAML identity[​](#enrich-payload-with-saml-identity "Direct link to Enrich payload with SAML identity") When you need the organization SAML identity in addition to GitHub’s `email`, enable `includeSAMLEmail` on the `user` and `team` kinds and map `.__SAMLEmail` (for example to a `samlEmail` property on your blueprint). The integration automatically detects where SAML is configured. It first checks the organization-level SAML identity provider. If no org-level SAML is found, it falls back to the enterprise-level SAML identity provider using the authenticated user’s enterprise membership. Enterprise-level SAML requirements To use enterprise-level SAML, you need a classic personal access token (PAT) with the `read:enterprise` scope, owned by an enterprise owner. GitHub Apps and fine-grained PATs do not support enterprise SAML identity queries. **Team member SAML identity enrichment (click to expand)** ``` - kind: user selector: query: 'true' includeSAMLEmail: true port: entity: mappings: properties: email: .email samlEmail: .__SAMLEmail - kind: team selector: query: 'true' members: true includeSAMLEmail: true port: entity: mappings: relations: members: '[.members.nodes[].login]' ``` #### Enrich payload with verified domain emails[​](#enrich-payload-with-verified-domain-emails "Direct link to Enrich payload with verified domain emails") For GitHub Enterprise Cloud organizations with [verified domains](https://docs.github.com/en/organizations/managing-organization-settings/verifying-or-approving-a-domain-for-your-organization), you can include each user’s verified domain emails by enabling `includeVerifiedDomainEmails` on the `user` kind. This adds the `organizationVerifiedDomainEmails` field (a list of email strings matching the organization’s verified domains) directly to the user payload. **User verified domain emails enrichment (click to expand)** ``` - kind: user selector: query: 'true' includeVerifiedDomainEmails: true port: entity: mappings: properties: email: .email verifiedEmails: .organizationVerifiedDomainEmails # Or pick the first verified email: # primaryVerifiedEmail: .organizationVerifiedDomainEmails[0] ``` #### Enrich payload with external IdP group[​](#enrich-payload-with-external-idp-group "Direct link to Enrich payload with external IdP group") For GitHub Enterprise Managed User (EMU) organizations with SCIM provisioning, you can enrich teams with the external identity provider group linked to each team. Enable `includeExternalGroup` on the `team` kind and map `.__external_group.group_id` and `.__external_group.group_name` to your blueprint properties (for example `externalGroupId` and `externalGroupName`). Use the following mapping configuration to include the external group data: **Team external IdP group enrichment (click to expand)** ``` - kind: team selector: query: 'true' members: true includeExternalGroup: true port: entity: mappings: identifier: .databaseId | tostring title: .name blueprint: '"githubTeam"' properties: slug: .slug description: .description link: .url permission: .permission notification_setting: .notificationSetting externalGroupId: .__external_group.group_id externalGroupName: .__external_group.group_name relations: members: '[.members.nodes[].login]' ``` ### Repositories, repository admins and users[​](#repositories-repository-admins-and-users "Direct link to Repositories, repository admins and users") The following example demonstrates how to ingest your GitHub repositories, their admins and related users to Port.
You can use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "admins": { "title": "Admins", "target": "githubUser", "required": false, "many": true } } } ``` **GitHub user blueprint (click to expand)** Create in Port ``` { "identifier": "githubUser", "title": "Github User", "icon": "Microservice", "schema": { "properties": { "email": { "title": "Email", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "user": { "title": "User", "target": "user", "required": false, "many": false } } } ``` **Users blueprint (click to expand)** Create in Port ``` { "identifier": "user", "title": "user", "icon": "Microservice", "schema": { "properties": { "email":{ "title":"Email", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Repository admin and user mapping (click to expand)** ``` createMissingRelatedEntities: true resources: - kind: repository selector: query: "true" includedRelations: collaborators: affiliation: "all" # "all" | "direct" | "outside" includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch relations: admins: "[.__collaborators[] | select(.is_admin == true) | .login | gsub("\\["; "-") | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "")]" - kind: user selector: query: "true" port: entity: mappings: identifier: .login | gsub("\\["; "-") | gsub("\\](?=[^$])"; "-") | gsub("\\]$"; "") title: if (.name != "" and .name) then .name else .login end blueprint: '"githubUser"' relations: user: .email - kind: user selector: query: "true" port: entity: mappings: identifier: .email title: .email blueprint: '"user"' properties: email: .email ``` #### Supported GitHub user types[​](#supported-github-user-types "Direct link to Supported GitHub user types") As GitHub has strict privacy policies, the GitHub API will only return emails in the following cases: 1. The user has a public email address. 2. Your organization is working with a GitHub Enterprise Cloud plan, and the user has an SAML SSO identity configured inside the GitHub organization. In other cases, the GitHub API will return a `null` value for the user's email. User supported fields For the `user` kind, only the following API fields are supported: `.name`, `.login`, and `.email`.
Other fields from the [GitHub User API](https://docs.github.com/en/rest/users#get-a-user) are not available. #### Enrich payload with SAML identity[​](#enrich-payload-with-saml-identity-1 "Direct link to Enrich payload with SAML identity") When you enable `includeSAMLEmail` on the `user` kind, the integration enriches the raw payload with `.__SAMLEmail` (the SAML `nameId` when available). The integration checks org-level SAML first, then automatically falls back to enterprise-level SAML if needed (see [requirements above](#enrich-payload-with-saml-identity)). **User SAML identity enrichment (click to expand)** ``` - kind: user selector: query: "true" includeSAMLEmail: "true" port: entity: mappings: properties: saml_email: .__SAMLEmail ``` ### Repositories and collaborators[​](#repositories-and-collaborators "Direct link to Repositories and collaborators") The following example demonstrates how to ingest your GitHub repositories and their collaborators to Port.
You can use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Collaborator blueprint (click to expand)** Create in Port ``` { "identifier": "githubCollaborator", "title": "Github Collaborator", "icon": "Github", "schema": { "properties": { "login": { "type": "string", "title": "Login" }, "url": { "type": "string", "title": "Url", "format": "url" }, "type": { "type": "string", "title": "Type" }, "site_admin": { "type": "boolean", "title": "Site admin" }, "role": { "type": "string", "title": "Role" } }, "required": [ "login" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "repository", "target": "githubRepository", "required": true, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` createMissingRelatedEntities: true resources: - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: collaborator selector: query: "true" port: entity: mappings: identifier: .__repository + "/" + .login title: .login blueprint: '"githubCollaborator"' properties: login: .login url: .html_url type: .type site_admin: .site_admin role: .role_name relations: repository: .__repository ``` ### Live events[​](#live-events "Direct link to Live events") Collaborator entities update in real time when a user's repository access changes, including when you revoke that access. Requires GitHub integration version `6.14.5` or later. The integration covers two revocation paths: * When you remove a team from a repository, it re-checks every member of that team against that repository. * When you remove a user from a team, it re-checks that user against every repository the team can access. In both cases the integration confirms a user's actual access before it deletes anything. A user who still reaches the repository another way keeps their collaborator entity. This includes organization owners, direct collaborators, and members of another team with access to the same repository. The integration deletes only the users whose access is fully revoked. If GitHub returns an error while it checks a specific repository, the integration skips that repository. Those collaborator entities stay unchanged until the next resync. ### Affiliation[​](#affiliation "Direct link to Affiliation") You can use the `affiliation` selector to filter which collaborators are ingested. * `all` (default): All collaborators * `direct`: Only direct collaborators * `outside`: Only outside collaborators ``` - kind: collaborator selector: query: "true" affiliation: "direct" ``` Live events with affiliation filtering Live events are **not supported** when `selector.affiliation` is set to `direct` or `outside`. To avoid catalog inconsistencies, the GitHub Ocean integration **skips collaborator live events** in this mode (webhook events are ignored) and **affiliation filtering is applied during resync only**. ## Security, deployments, and releases[​](#security-deployments-and-releases "Direct link to Security, deployments, and releases") The following examples demonstrate how to ingest security alerts, deployments, releases, and related repository metadata into Port. ### Repositories, dependabot alerts, code and secrets scan alerts[​](#repositories-dependabot-alerts-code-and-secrets-scan-alerts "Direct link to Repositories, dependabot alerts, code and secrets scan alerts") The following example shows how to ingest your GitHub repositories and their alerts (Dependabot and Code scan alerts) into Port. You can use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Dependabot alert blueprint (click to expand)** Create in Port ``` { "identifier": "githubDependabotAlert", "title": "Dependabot Alert", "icon": "Github", "schema": { "properties": { "severity": { "title": "Severity", "type": "string", "enum": ["low", "medium", "high", "critical"], "enumColors": { "low": "yellow", "medium": "orange", "high": "red", "critical": "red" }, "icon": "DefaultProperty" }, "state": { "title": "State", "type": "string", "enum": ["auto_dismissed", "dismissed", "fixed", "open"], "enumColors": { "auto_dismissed": "green", "dismissed": "green", "fixed": "green", "open": "red" }, "icon": "DefaultProperty" }, "packageName": { "icon": "DefaultProperty", "title": "Package Name", "type": "string" }, "packageEcosystem": { "title": "Package Ecosystem", "type": "string" }, "manifestPath": { "title": "Manifest Path", "type": "string" }, "scope": { "title": "Scope", "type": "string" }, "ghsaID": { "title": "GHSA ID", "type": "string" }, "cveID": { "title": "CVE ID", "type": "string" }, "url": { "title": "URL", "type": "string", "format": "url" }, "references": { "icon": "Vulnerability", "title": "References", "type": "array", "items": { "type": "string", "format": "url" } }, "alertCreatedAt": { "icon": "DefaultProperty", "type": "string", "title": "Alert Created At", "format": "date-time" }, "alertUpdatedAt": { "icon": "DefaultProperty", "type": "string", "title": "Alert Updated At", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": false } } } ``` **Code scanning alert blueprint (click to expand)** Create in Port ``` { "identifier": "githubCodeScanAlert", "title": "Code Scan Alerts", "icon": "Microservice", "schema": { "properties": { "description": { "title": "Alert description", "type": "string" }, "severity": { "title": "Alert severity", "type": "string" }, "tags": { "items": { "type": "string" }, "title": "Alert tags", "type": "array" }, "url": { "title": "alert URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": false } } } ``` **Secret scanning alert blueprint (click to expand)** Create in Port ``` { "identifier": "githubSecretScanAlert", "title": "Secret Scan Alerts", "icon": "Microservice", "schema": { "properties": { "number": { "title": "Alert Number", "type": "number" }, "state": { "title": "Alert State", "type": "string" }, "secret_type": { "title": "Secret Type", "type": "string" }, "secret": { "title": "Secret", "type": "string" }, "url": { "title": "alert URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": true, "many": true } } } ``` **Repository security alert mappings (click to expand)** ``` resources: - kind: repository selector: query: "true" includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: dependabot-alert selector: query: "true" states: ["open", "fixed"] # other options: auto_fixed, dismissed port: entity: mappings: identifier: .__repository + "/" + (.number | tostring) title: .number | tostring blueprint: '"githubDependabotAlert"' properties: state: .state severity: .security_advisory.severity packageName: .dependency.package.name packageEcosystem: .dependency.package.ecosystem manifestPath: .dependency.manifest_path scope: .dependency.scope ghsaID: .security_advisory.ghsa_id cveID: .security_advisory.cve_id url: .html_url references: "[.security_advisory.references[].url]" alertCreatedAt: .created_at alertUpdatedAt: .updated_at relations: repository: .__repository - kind: code-scanning-alerts selector: query: "true" state: "dismissed" # other options: open, closed, fixed port: entity: mappings: identifier: .__repository + "/" + (.number | tostring) title: .rule.name blueprint: '"githubCodeScanAlert"' properties: state: .state severity: .rule.severity tags: .rule.tags description: .rule.description url: .html_url relations: repository: .__repository - kind: secret-scanning-alerts selector: query: 'true' state: all hideSecret: false port: entity: mappings: identifier: .__repository + "/" + (.number | tostring) title: .number | tostring blueprint: '"githubSecretScanAlert"' properties: state: .state secret_type: .rule.secret_type url: .url relations: repository: .__repository ``` * Dependabot severity * Dependabot ecosystems * Code scanning severity You can use the `severity` selector to filter dependabot alerts by severity levels.
An alert is ingested if its severity is **any one** of the severities you set (OR logic across the list). Valid values: `low`, `medium`, `high`, `critical`. ``` - kind: dependabot-alert selector: query: "true" states: ["open", "fixed"] severity: ["high", "critical"] ``` You can use the `ecosystems` selector to filter dependabot alerts by package ecosystems.
An alert is ingested if its ecosystem is **any one** of the ecosystems you set (OR logic across the list). Valid values: `composer`, `go`, `maven`, `npm`, `nuget`, `pip`, `pub`, `rubygems`, `rust`. ``` - kind: dependabot-alert selector: query: "true" states: ["open", "fixed"] ecosystems: ["npm", "pip"] ``` You can use the `severity` selector to filter code scanning alerts by severity level.
An alert is ingested only if its severity matches the value you set. Valid values: `critical`, `high`, `medium`, `low`, `warning`, `note`, `error`. ``` - kind: code-scanning-alerts selector: query: "true" state: "open" severity: "high" ``` Withdrawn Security Advisories When a security advisory is withdrawn, GitHub deletes the associated Dependabot alert but does not send a `dependabot_alert` webhook event. This means Port is not notified in real-time about the alert's removal. As a result, the alert entity might persist in Port until a full resync is performed. A full resync fetches all alerts from the GitHub API, and since withdrawn alerts are no longer returned, they will be removed from Port. For more context, you can follow the [community discussion on GitHub](https://github.com/orgs/community/discussions/151862) regarding this behavior. ### Repositories, deployments, deployment statuses and environments[​](#repositories-deployments-deployment-statuses-and-environments "Direct link to Repositories, deployments, deployment statuses and environments") The following example demonstrates how to ingest your GitHub repositories, their deployments, deployment statuses and environments to Port, you may use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Environment blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepoEnvironment", "title": "Deployment Environments", "icon": "Environment", "schema": { "properties": { "url": { "icon": "DefaultProperty", "title": "URL", "type": "string", "format": "url" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time", "icon": "DefaultProperty" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "protectedBranches": { "title": "Protected Branches", "type": "boolean" }, "customBranchPolicies": { "title": "Custom Branch Policies", "type": "boolean" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "target": "githubRepository", "required": true, "many": false } } } ``` **Deployment blueprint (click to expand)** Create in Port ``` { "identifier": "githubDeployment", "title": "Deployment", "icon": "Deployment", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "ref": { "title": "Ref", "type": "string" }, "sha": { "title": "Sha", "type": "string" }, "transientEnvironment": { "title": "Transient Running Service", "type": "boolean" }, "productionEnvironment": { "title": "Production Running Service", "type": "boolean" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "url": { "title": "URL", "type": "string", "icon": "Link", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "deployment_environment": { "title": "Deployment Environment", "target": "githubRepoEnvironment", "required": false, "many": false } } } ``` **Deployment status blueprint (click to expand)** Create in Port ``` { "identifier": "githubDeploymentStatus", "title": "Deployment Status", "icon": "Deployment", "schema": { "properties": { "state": { "title": "State", "type": "string", "enum": ["success", "failure", "pending", "in_progress", "queued", "error"], "enumColors": { "success": "green", "failure": "red", "pending": "yellow", "in_progress": "blue", "queued": "purple", "error": "red" } }, "description": { "title": "Description", "type": "string" }, "environment": { "title": "Environment", "type": "string" }, "logUrl": { "title": "Log URL", "type": "string", "format": "url" }, "environmentUrl": { "title": "Environment URL", "type": "string", "format": "url" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "deployment": { "title": "Deployment", "target": "githubDeployment", "required": false, "many": false } } } ``` **Repository, deployment, and environment mappings (click to expand)** ``` createMissingRelatedEntities: true resources: - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md port: entity: mappings: identifier: ".name" # The Entity identifier will be the repository name. title: ".name" blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: environment selector: query: "true" port: entity: mappings: identifier: .__repository + "-" + .name title: .name blueprint: '"githubRepoEnvironment"' properties: url: .html_url customBranchPolicies: .deployment_branch_policy.custom_branches_policies protectedBranches: .deployment_branch_policy.protected_branches createdAt: .created_at updatedAt: .updated_at relations: repository: .__repository - kind: deployment selector: query: "true" port: entity: mappings: identifier: .__repository + "-" + (.id|tostring) title: .task + "-" + .environment blueprint: '"githubDeployment"' properties: description: .description ref: .ref sha: .sha productionEnvironment: .production_environment transientEnvironment: .transient_environment createdAt: .created_at url: .url relations: deployment_environment: .__repository + "-" + .environment - kind: deployment-status selector: query: "true" port: entity: mappings: identifier: .__repository + "-" + (.__deployment_id|tostring) + "-" + (.id|tostring) title: .state + " - " + .environment blueprint: '"githubDeploymentStatus"' properties: state: .state description: .description environment: .environment logUrl: .log_url environmentUrl: .environment_url createdAt: .created_at updatedAt: .updated_at relations: deployment: .__repository + "-" + (.__deployment_id|tostring) ``` * Task * Environment You can use the `task` selector to filter deployments by task name (for example, `deploy` or `deploy:migrations`). ``` - kind: deployment selector: query: "true" task: "deploy" ``` You can use the `environment` selector to filter deployments by environment name (for example, `staging` or `production`). ``` - kind: deployment selector: query: "true" environment: "production" ``` Same selectors as deployment The `deployment-status` kind supports the same `task` and `environment` selectors as the `deployment` kind. #### First commit enrichment for lead time[​](#first-commit-enrichment-for-lead-time "Direct link to First commit enrichment for lead time") When you set `enrichWithFirstCommit` to `true` on the `deployment` selector, the integration attaches a `__firstCommit` object to each deployment payload with the earliest commit shipped since the previous deployment to the same environment (`__sha` and `__timestamp` in UTC), plus a deployment-level `__commitCount`.
You can use these fields to compute [DORA Lead Time for Changes](https://docs.port.io/solutions/engineering-intelligence/measure-and-track-standards/dora-metrics) at the deployment level, measured from the first commit to the deployment's creation time. To resolve the first commit, the integration compares each deployment against the previous deployment to the same environment. When there is no previous deployment to compare against, it falls back to the commit referenced by the deployment's own `sha`. When no first-commit timestamp is available, the `leadTimeHours` property below resolves to `null`. To store the result, add a `leadTimeHours` number property to your deployment blueprint: **Lead time property (click to expand)** ``` { "leadTimeHours": { "type": "number", "title": "Lead Time (Hours)", "description": "Hours from the first commit shipped in the deployment to the deployment's creation time" } } ``` The following configuration enables the enrichment and maps `leadTimeHours`: **Deployment first commit enrichment mapping (click to expand)** ``` - kind: deployment selector: query: 'true' enrichWithFirstCommit: true port: entity: mappings: identifier: .__repository + "-" + (.id|tostring) title: .task + "-" + .environment blueprint: '"githubDeployment"' properties: description: .description ref: .ref sha: .sha createdAt: .created_at url: .url leadTimeHours: >- if .__firstCommit.__timestamp and .created_at then ((((.created_at | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601) - (.__firstCommit.__timestamp | sub("\\.[0-9]+Z$"; "Z") | fromdateiso8601)) / 3600) * 100 | floor) / 100 else null end relations: deployment_environment: .__repository + "-" + .environment ``` Performance impact Enabling `enrichWithFirstCommit` on the `deployment` kind adds one extra API call per deployment to fetch its changes. This can slow down large resyncs. Leave it disabled unless you need deployment-level lead time. ### Repositories, repository releases and tags[​](#repositories-repository-releases-and-tags "Direct link to Repositories, repository releases and tags") The following example demonstrates how to ingest your GitHub repositories, their releases and tags to Port, you may use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **Tag blueprint (click to expand)** Create in Port ``` { "identifier": "githubTag", "title": "Tag", "icon": "Github", "schema": { "properties": { "commit_sha": { "icon": "DefaultProperty", "type": "string", "title": "Commit sha" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false } } } ``` **Release blueprint (click to expand)** Create in Port ``` { "identifier": "githubRelease", "title": "Release", "icon": "Github", "schema": { "properties": { "release_creation_time": { "icon": "DefaultProperty", "type": "string", "title": "Release creation time", "format": "date-time" }, "author": { "type": "string", "title": "Author" }, "description": { "type": "string", "title": "Description" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "githubRepository", "required": false, "many": false }, "tag": { "title": "Tag", "target": "githubTag", "required": false, "many": false } } } ``` **Repository, release, and tag mappings (click to expand)** ``` resources: - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md port: entity: mappings: identifier: ".name" # The Entity identifier will be the repository name. title: ".name" blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: tag selector: query: 'true' port: entity: mappings: identifier: .name title: .name blueprint: '"githubTag"' properties: commit_sha: .commit.sha relations: repository: .__repository - kind: release selector: query: 'true' port: entity: mappings: identifier: .name title: .name blueprint: '"githubRelease"' properties: author: .author.login description: .body release_creation_time: .created_at relations: tag: .tag_name repository: .__repository ``` ### Repositories and software bill of materials (SBOM)[​](#repositories-and-software-bill-of-materials-sbom "Direct link to Repositories and software bill of materials (SBOM)") The following example demonstrates how to ingest your GitHub repositories and their software bill of materials (SBOM) to Port.
You can use the following Port blueprint definitions and `port-app-config.yml`: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "organization": { "title": "Organization", "target": "githubOrganization", "required": false, "many": false } } } ``` **SBOM blueprint (click to expand)** Create in Port ``` { "identifier": "githubSBOM", "title": "Github Dependency Graph", "icon": "Github", "schema": { "properties": { "name": { "type": "string", "title": "Name" }, "spdxid": { "type": "string", "title": "SPDXID", }, "version": { "type": "string", "title": "Version" }, "license_concluded": { "type": "string", "title": "License" }, "copyright_text": { "type": "string", "title": "Copyright" }, "external_refs": { "type": "array", "items": { "type": "object" } } }, "required": [ "spdxid" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "repository", "target": "githubRepository", "required": true, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` createMissingRelatedEntities: true resources: - kind: repository selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. includedFiles: - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] url: .html_url defaultBranch: .default_branch - kind: repository selector: query: "true" includedRelations: sbom: true port: itemsToParse: .__sbom.packages itemsToParseTopLevelTransform: true entity: mappings: identifier: .item.SPDXID title: .item.name blueprint: '"githubSBOM"' properties: name: .item.name spdxid: .item.SPDXID version: .item.versionInfo license_concluded: .item.licenseConcluded copyright_text: .item.copyrightText external_refs: .item.externalRefs relations: repository: .name ``` ### Repositories and custom properties[​](#repositories-and-custom-properties "Direct link to Repositories and custom properties") To ingest your GitHub repositories along with their [organization custom properties](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization) into Port, you have to enable the feature explicitly. Set `customProperties: true` in `includedRelations` to fetch custom property values for each repository via the GitHub API. The properties are returned as a key-value object (e.g. `{"environment": "production", "team": "backend"}`). This is especially useful when using `repoSearch` in your selector, since the GitHub Search API does not include custom properties in its response. Enabling this enrichment ensures custom properties are available regardless of the fetch strategy. Resync required for custom property updates Changes to repository custom property values are **not** processed via live events (webhooks). Although GitHub emits `custom_property_values` webhook events when property values change, the GitHub Ocean integration does not currently consume these events. Custom property updates are only reflected in Port during scheduled resyncs or manual resyncs. If you need near real-time updates for custom properties, consider reducing your `scheduledResyncInterval`. You can use the following Port blueprint definitions and `port-app-config.yml`: **Repository with custom properties blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" }, "custom_properties": { "title": "Custom Properties", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: repository selector: query: "true" includedRelations: customProperties: true port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: file://README.md url: .html_url defaultBranch: .default_branch custom_properties: .custom_properties ``` ### Repositories and GitHub pages[​](#repositories-and-github-pages "Direct link to Repositories and GitHub pages") You can optionally enrich repository payloads with GitHub Pages configuration by seting `pages: true` in `includedRelations`. This fetches the Pages configuration for each repository via the GitHub API. The response is added to the repository raw payload under `.__pages` (or `{}` when Pages is not enabled for the repository). See GitHub’s API reference for the response shape: [Get a GitHub Pages site](https://docs.github.com/en/rest/pages#get-a-github-pages-site). You can use the following Port blueprint definitions and `port-app-config.yml`: **Repository with pages blueprint (click to expand)** Create in Port ``` { "identifier": "githubRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "Repository URL", "type": "string", "format": "url" }, "defaultBranch": { "title": "Default branch", "type": "string" }, "pages": { "title": "Pages", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: repository selector: query: "true" includedRelations: pages: true port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: url: .html_url defaultBranch: .default_branch pages: .__pages ``` --- # GitOps Port's GitHub integration allows you to manage Port entities with a GitOps approach, making your code repositories the source of truth for your infrastructure assets. Some common use cases include: * Use GitHub as the source-of-truth for your **microservices**, **packages**, **libraries** and other software catalog assets. * Allow developers to keep the catalog up-to-date, by making updates to files in their Git repositories. * Create a standardized way to document software catalog assets in your organization. Port offers two ways to manage entities using GitOps: 1. Using a dedicated `port.yml` file in your repository. 2. Using the GitHub integration mapping in your portal. ## Option 1: Use a `port.yml` file[​](#option-1-use-a-portyml-file "Direct link to option-1-use-a-portyml-file") This approach requires adding a `port.yml` file to the **default branch** (usually `main`) of your repository. Note that the `port.yml` file is not the same as the [`port-app-config.yml` file](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/#port-app-configyml-file) used to configure the GitHub integration, and does not replace it. The `port.yml` file can specify one or more Port entities that will be ingested to Port. Any change made to the `port.yml` file will also be reflected inside Port. GitHub integration To manage entities using GitOps and the `port.yml` file, Port's [GitHub integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/#setup) must be installed, as it listens to `push` events sent from Github. This means that if the `port.yml` file exists in the repository before installing the integration, it will not be picked up automatically. You will need to make some update to the `port.yml` file and push it to the repository in order for the Git integration to properly track and ingest the entity information. The `port.yml` file is how you specify your Port entities that are managed using GitOps and whose data is ingested from your Git repositories. Here are examples for valid `port.yml` files: * Single entity * Multiple entities ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ``` - identifier: myEntity1 title: My Entity1 blueprint: myFirstBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 - identifier: myEntity title: My Entity2 blueprint: mySecondBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ``` Since both of the valid `port.yml` formats follow the same structure, the following section will explain the format based on the single entity example. ### `port.yml` structure[​](#portyml-structure "Direct link to portyml-structure") Here is an example `port.yml` file: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` * The `identifier` key is used to specify the identifier of the entity that the app will create and keep up-to-date when changes occur: ``` identifier: myEntity title: My Entity ... ``` * The `title` key is used to specify the title of the entity: ``` identifier: myEntity title: My Entity ... ``` * The `blueprint` key is used to specify the identifier of the blueprint to create this entity from: ``` ... title: My Entity blueprint: myBlueprint ... ``` * The `properties` key is used to map the values to the different properties of the entity: ``` ... title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ... ``` * The `relations` key is used to map target entities to the different relations of the entity: * Single relation * Many relation ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity ``` ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ### Setting null properties[​](#setting-null-properties "Direct link to Setting null properties") When you want to clear a property's value in Port, you can explicitly set it to `null` in your `port.yml` file.
For example: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: description: null owner: null ``` Difference between null and omitting When you omit a property entirely from the `port.yml`, Port will keep its existing value. Setting a property to `null` explicitly tells Port to clear that property's value. ### Ingesting repository file contents[​](#ingesting-repository-file-contents "Direct link to Ingesting repository file contents") It is possible to use the contents of files in the repository as the value for entity properties using a simple reference. The following example will read the string contents of `~/module1/README.md` and upload it to `myStringProp` of the specified entity. Repository folder structure used for the example: ``` root | +- port.yml | +-+ module1 | | | +- README.md | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: myStringProp: file://module1/README.md ``` #### Using relative paths[​](#using-relative-paths "Direct link to Using relative paths") It is also possible to use paths relative to the location of the `port.yml` spec file. For example: `file://./` is used to reference a file in the same directory as the `port.yml` file. `file://../` is used to reference a file that is one directory above and so on. The following example reads `README.md` and `module1/requirements.txt` using paths relative to `port.yml` Repository folder structure used for the example: ``` root | +-+ meta | | | +-- port.yml | | | +-+ README.md | +-+ module1 | | | +- requirements.txt | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: readme: file://./README.md module1Requirements: file://../module1/requirements.txt ``` ## Option 2: Use the integration mapping[​](#option-2-use-the-integration-mapping "Direct link to Option 2: Use the integration mapping") Every integration in Port has a dedicated [mapping configuration](/context-lake/ingestion/configure-mapping/overview/.md) that allows you to specify which resources to ingest from the integration into Port. In the case of the GitHub integration, one of the supported resources is the `file` resource, which allows you to ingest file contents from a repository into your portal. To use this approach, you will need to edit your GitHub integration mapping and add a `file` block that specifies which files to ingest.
To edit a mapping configuration: 1. Go to the [connectors page](https://app.port.io/settings/data-sources) of your portal. 2. Under `Exporters`, find the GitHub connector and click on it. 3. Scroll down to the `Mapping` section and add a `file` block to the `resources` array. For example, say you want to ingest a `package.json` file form your repository. You can add the following to your GitHub integration mapping: Organization field for file selectors Optional if `githubOrganization` is set; required only when not set. ``` resources: ... - kind: file selector: query: 'true' files: - path: package.json organization: my-org # Optional if githubOrganization is set (required if not set) repos: - name: my-repo branch: main port: entity: mappings: identifier: .path blueprint: '"file"' properties: content: .content ``` The `selector.files.path` key also supports glob patterns, so you can ingest multiple files by matching against a pattern and create an entity in Port for each one, for example: ``` - kind: file selector: query: 'true' files: - path: 'resources/*.yml' organization: my-org # Optional if githubOrganization is set; required if not set repos: - name: my-repo branch: main ``` ### Advantages[​](#advantages "Direct link to Advantages") * **Resync support**: Since this approach uses the integration mapping, a resync of the integration will update the entities in Port with the latest file contents. * **Data manipulation**: Since this approach uses the integration mapping, `jq` is supported and can be used to transform the file contents before ingestion. ## Examples[​](#examples "Direct link to Examples") Check out the [example repository](https://github.com/port-labs/github-app-setup-example) for a microservice blueprint and a matching `port.yml` file which specifies a microservice entity. ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Port.yml ingestion pattern[​](#portyml-ingestion-pattern "Direct link to Port.yml ingestion pattern") The `port.yml` ingestion pattern allows you to configure the GitHub integration to ingest `port.yml` files as part of the resync process. This approach is particularly useful when you want to maintain data integrity and ensure that your `port.yml` files are properly synchronized with Port. `port.yml` files operate in the GitOps methodology, meaning they are ingested into Port whenever a commit to the main branch of the repository is detected. Here's how to configure the integration to parse and ingest `port.yml` files: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: entity: mappings: identifier: .content.identifier title: .content.title blueprint: .content.blueprint properties: property_a: .content.properties.property_a property_b: .content.properties.property_b relations: relation_a: .content.relations.relation_a relation_b: .content.relations.relation_b ``` For multiple entities in a single `port.yml` file, use the `itemsToParse` key: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: itemsToParse: .content entity: mappings: identifier: .item.identifier title: .item.title blueprint: .item.blueprint properties: property_a: .item.properties.property_a property_b: .item.properties.property_b relations: relation_a: .item.relations.relation_a relation_b: .item.relations.relation_b ``` --- # Key changes This page covers the improvements and mapping changes introduced in the GitHub Ocean integration compared to the legacy GitHub app. Use it as a reference when updating your integration configuration. A key change is how custom attributes are denoted. 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. For a conceptual overview of attaching files with `includedFiles` and JQ (the replacement for legacy `file://` in mappings), see [Enrich entities with file contents](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/capabilities/.md#enrich-entities-with-file-contents). ## Improvements[​](#improvements "Direct link to Improvements") The GitHub Ocean integration introduces several key improvements over the legacy GitHub app. ### Multi-organization support[​](#multi-organization-support "Direct link to Multi-organization support") The GitHub integration supports ingesting data from multiple GitHub organizations starting from **version 3.0.0-beta**. Configure one organization using `githubOrganization` in your environment variables, or list multiple organizations in your port mapping under `organizations`: ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true organizations: - org1 - org2 # ... rest of your mapping (repositoryType, resources, etc.) ... ``` **Precedence:** If `githubOrganization` is set in the environment variables or config and `organizations` are listed in port mapping, the integration syncs only the `githubOrganization` (single-org behavior). ### Authentication model[​](#authentication-model "Direct link to 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](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md). Classic PAT required for multi-org For multi-organization support, you must use a **classic Personal Access Token**. Fine-grained PAT tokens do not work with multi-organization configurations. Below is a sample Helm value for this configuration: ``` integration: secrets: githubToken: "" ``` #### 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 in the [installation guide](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md?auth=custom-github-app) under the Custom GitHub App tab. Single organization limitation GitHub App authentication only supports **one organization** at a time. You must specify exactly one organization using `githubOrganization`. Below is a sample Helm value for this configuration: ``` integration: config: githubAppId: "" # app client id also works githubOrganization: "my-org" # Required for single organization support regardless of token type secrets: githubAppPrivateKey: "" ``` ### Webhooks[​](#webhooks "Direct link to 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, refer to the [live events documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md). Personal account webhook limitation For GitHub **personal accounts** (user-owned repositories), GitHub does not support an equivalent of organization-level webhooks (there is no `/users/{user}/hooks`). This means the integration cannot subscribe once at the account level to reliably receive events for **new repositories created later** in that personal account. As a result, newly created user-owned repositories may not be ingested into Port immediately via live events, and may only appear after a resync or repository discovery cycle. If you have an automation (for example, a scaffolding workflow) that creates an entity with a relation to a `githubRepository` entity right after creating a new GitHub repository, ensure that the related repository entity is created first (or wait for a resync) to avoid relation failures due to a missing related `githubRepository`. ### Deployment[​](#deployment "Direct link to Deployment") We've expanded our self-hosted installation examples to support deploying on a Kubernetes cluster using Helm or ArgoCD. For more details, refer to the [deployment documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md#setup). ### Workflow runs[​](#workflow-runs "Direct link to 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[​](#repository-type "Direct link to Repository type") You can now specify the type of repositories (`public`, `private`, or `all`) from which to ingest data. All other data kinds 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[​](#kind-mapping-changes "Direct link to Kind mapping changes") The data blueprints for GitHub have been updated to provide cleaner data structures and improved relationships between different software catalog entities. ### Files & GitOps[​](#files--gitops "Direct link to Files & GitOps") Organization field in file selectors The `organization` field is optional when `githubOrganization` is set in the environment variables and it is required when not provided there. **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" organization: my-org # Optional if githubOrganization is set; required if not set # 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 `organization` field can be specified per file pattern when no global organization is configured. 2. 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. 3. 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`. 4. The `repo` key has been renamed to `repository` when referencing the repository a file belongs to, for consistency with other data kinds. ### Repository relationships[​](#repository-relationships "Direct link to Repository relationships") Fetching related data for a repository, like teams and collaborators, is now managed through a unified `includedRelations` 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" includedRelations: # ✅ new teams: true includedFiles: # ✅ new, replaces file:// prefix - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] # ✅ changed, was 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" includedRelations: # ✅ new collaborators: affiliation: "all" # "all" | "direct" | "outside" includedFiles: # ✅ new, replaces file:// prefix - README.md port: entity: mappings: identifier: .name title: .name blueprint: '"githubRepository"' properties: readme: .__includedFiles["README.md"] # ✅ changed, was file://README.md url: .html_url defaultBranch: .default_branch relations: collaborators: "[.__collaborators[].login]" # ✅ new ``` ### Issues[​](#issues "Direct link to 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" 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[​](#pull-requests "Direct link to 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). With `api: graphql`, you can set `enrichWithFirstCommit: true` so each pull request includes a `firstCommit` object (`oid`, `committedDate`) for the first commit on the branch. See the [GitHub Ocean examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/examples/.md#first-commit-on-the-pull-request-branch) page for details. **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[​](#folders "Direct link to Folders") Organization field in folder selectors The `organization` field is optional when `githubOrganization` is set in the environment variables and is required when not provided (e.g., Classic PAT with multiple organizations defined in your port mapping). 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/* organization: my-org # Optional if githubOrganization is set; required if not set repos: - name: backend-service # ✅ new, now has a 'name' key branch: main # ✅ new, optional branch name includedFiles: # ✅ new, replaces file:// prefix - README.md 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: .__includedFiles["README.md"] # ✅ changed, was file://README.md ``` ### Teams[​](#teams "Direct link to 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 team ID you see may differ depending on whether you are fetching team 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. Team IDs in REST and GraphQL The `members` selector defaults to `true` when omitted. When `members: true`, the integration fetches teams using the GitHub GraphQL API. * In GraphQL, `id` is a global node ID (string). * In GraphQL, `databaseId` is the numeric ID that matches the team `id` returned by the GitHub REST API. If you need the numeric team ID (for example, to match an existing REST-based identifier), use `databaseId` in your mappings. **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: members: "[.members[].login]" # ❌ changed ``` **New configuration (click to expand)** ``` - kind: team selector: query: "true" members: true # ✅ unchanged port: entity: mappings: identifier: .databaseId | tostring title: .name blueprint: '"githubTeam"' properties: slug: .slug description: .description link: .url relations: members: "[.members.nodes[].login]" # ✅ new, nodes subarray ``` ## Other changes[​](#other-changes "Direct link to Other changes") ### `dependabot-alert`[​](#dependabot-alert "Direct link to 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`[​](#code-scanning-alerts "Direct link to 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 ``` ### `secret-scanning-alerts`[​](#secret-scanning-alerts "Direct link to secret-scanning-alerts") The `secret-scanning-alerts` kind is now available in Ocean GitHub. You can filter by a single `state` (e.g., `open`) and control whether the secret content is included using the `hideSecret` option (defaults to `true`, which hides the secret): ``` resources: - kind: secret-scanning-alerts selector: query: "true" state: open # ["open", "resolved", "all"] hideSecret: true # (default: true) ``` ## Summary of key changes[​](#summary-of-key-changes "Direct link to Summary of key changes") | Area | Old value | New value | Notes | | ---------------------------- | ------------------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Multi-organization** | N/A | `githubOrganization` is not optional | Classic PAT supports multiple orgs using the `organization` parameter in port mapping. GitHub App and fine-grained PAT do not support multi-organization and require the `githubOrganization` configuration. Syncing multiple organizations increases API calls and may slow down the integration. | | **File organization** | N/A | `organization: "my-org"` | Optional if `githubOrganization` is set, required when not (e.g., Classic PAT multi-org). | | **Folder organization** | N/A | `organization: "my-org"` | Optional if `githubOrganization` is set, required when not set (e.g., Classic PAT multi-org). | | **Authentication** | GitHub App installation | PAT or self-created GitHub App | The integration can be authenticated using a Personal Access Token (PAT) or a self-created GitHub App. Multi-org requires classic PAT. | | **Webhooks** | App webhook | Automatic setup by integration | The integration now manages its own webhooks for live events. This requires `webhook` permissions and `liveEvents.baseUrl` to be set. | | **Workflow runs** | 10 per repository | 100 per workflow | The number of ingested workflow runs has been increased. | | **Repository type** | N/A | `repositoryType` configuration | A new top-level configuration is available to filter repositories by type (`public`, `private`, or `all`). | | **Repository relationships** | `teams: true`, `collaborators: true` | `includedRelations` | Use `includedRelations` to fetch related data (teams/collaborators/SBOM). This replaces the old boolean flags for each relationship type. The fetched data is prefixed with `__` (e.g., `.__teams`). | | **Pull requests** | N/A | `states`, `maxResults`, `since` selectors | New selectors are available for more granular filtering. | | **File properties** | `.file.path` | `.path` | All file properties are now at the top level of the object, no longer nested under `.file`. | | **Repository reference** | `.repo` or `.head.repo.name` | `.__repository` | The integration now consistently provides repository information under the `__repository` field for all relevant kinds. | | **Folder name** | `.folder.name` | `.folder.path \| split('/') \| last` | The folder name is no longer directly available and should be derived from the folder path using a JQ expression. | --- # Migration steps This guide is the customer checklist for migrating from Port's legacy GitHub app (Probot) to [GitHub Ocean](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md). Complete it before **October 15, 2026**, when Port turns on the sunset experience for leftover legacy installs. Complete this checklist before October 15, 2026 Starting **October 15, 2026**, leftover legacy GitHub integrations become read-only in Port. You cannot resync them or apply mapping changes, and self-service actions or automations that still use the legacy `GITHUB` backend fail closed. After Port scales the legacy service down, webhooks to the old app stop delivering, so the catalog goes stale until you finish this migration. ## Customer checklist[​](#customer-checklist "Direct link to Customer checklist") Follow these steps in order: 1. **Install GitHub Ocean** - for hosted installs, disable **Create default resources**; for self-hosted, set `createPortResourcesOrigin: Empty`. 2. **Transfer entity ownership** - run `port-github-migrator` before you uninstall the old app. 3. **Rewrite actions and automations** - change `GITHUB` backends to `INTEGRATION_ACTION` with your Ocean `installationId` (for example `dispatch_workflow`). 4. **Uninstall the legacy GitHub App in GitHub** - Port does not uninstall it for you. ### What Port does vs what you do[​](#what-port-does-vs-what-you-do "Direct link to What Port does vs what you do") | Owner | Migration tasks | | ----- | -------------------------------------------------------------------------------------------------------------------------------------------- | | Port | Soft-deprecate leftover legacy integrations in Port (`deprecated: true`), keep them visible as read-only, and block resync / mapping writes. | | You | Install GitHub Ocean, transfer ownership, rewrite actions/automations, and uninstall the GitHub App in GitHub. | Port does **not** delete your catalog entities or uninstall the GitHub App from your organization. If you skip migration, entities stay in Port but stop receiving live updates once the legacy webhook path freezes. ### Before you start[​](#before-you-start "Direct link to Before you start") Before the detailed steps below, review the [key changes](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/migration/key-changes/.md) page so you know how blueprints and selectors differ between the two integrations. Entity ownership transfer Only the integration that created an entity can delete it. Transfer ownership with the migrator **before** uninstalling the old app. If you uninstall first, entities created by the legacy integration become orphaned and you must delete them manually. ## Step-by-step migration plan[​](#step-by-step-migration-plan "Direct link to Step-by-step migration plan") This section provides the practical steps to migrate your entities and transfer ownership to the new GitHub Ocean integration. ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") #### Get your installation IDs[​](#get-your-installation-ids "Direct link to Get your installation IDs") Before migrating, you need to identify both installation IDs. Note that these are **different types of values**: | Flag | Type | Example | Where to find it | | ----------------------- | -------------------------------------- | ----------------- | --------------------------- | | `--old-installation-id` | **Numeric** GitHub App installation ID | `12345678` | GitHub App installation URL | | `--new-installation-id` | **String** Port integration identifier | `github-ocean-v2` | Port UI or API (see below) | These are not the same type of value A common mistake is passing a numeric GitHub App installation ID for `--new-installation-id`. This will cause `get-diff` to return empty results (all entities appear as "not migrated") because the migrator cannot find the new integration. The `--new-installation-id` must be the **string identifier** of your Port integration, not a numeric ID. **Finding the legacy GitHub App installation ID (numeric):** 1. Navigate to GitHub App installations: * **Personal installations**: `https://github.com/settings/installations` * **Organization installations**: `https://github.com/organizations/YOUR-ORG/settings/installations` 2. Click on Port's GitHub App installation from the list. 3. Look at the **URL in your browser address bar** - it contains the installation ID at the end. **Visual guide:** ![GitHub App installation ID in the URL bar](/img/build-your-software-catalog/sync-data-to-catalog/github/githubAppInstallationId.png) The installation ID is the numeric value circled in the URL bar (e.g., `97269548` in the example above). **Finding the new GitHub Ocean integration identifier (string):** The `--new-installation-id` is the **name you gave to the GitHub Ocean integration** when you created it in Port (e.g., `github-ocean`, `github-ocean-v2`, `my-github-integration`). You can find it in one of these ways: * **Port UI**: Go to the [connectors page](https://app.port.io/settings/data-sources) and look for the integration name. * **Port API**: Call the integrations endpoint and look for the `installationId` field of your GitHub Ocean integration: ``` curl -s https://api.port.io/v1/integration \ -H "Authorization: Bearer YOUR_ACCESS_TOKEN" | jq '.integrations[] | {installationId, integrationType}' ``` Replace `YOUR_ACCESS_TOKEN` with your [Port API access token](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials). #### Install the migrator tool[​](#install-the-migrator-tool "Direct link to Install the migrator tool") ``` curl -sL https://raw.githubusercontent.com/port-labs/port-github-migrator/main/install.sh | bash ``` Gather your Port API credentials: * Port API client ID. * Port API client secret. * Your Port instance URL. For detailed command reference and usage examples, refer to the [port-github-migrator GitHub repository](https://github.com/port-labs/port-github-migrator#commands-reference). The repository contains complete documentation for all available commands. ### Set up the new integration[​](#set-up-the-new-integration "Direct link to Set up the new integration") Follow the [installation guide](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md). **For Port-hosted deployments: disable "Create default resources"** When installing the new GitHub Ocean integration via Port UI while migrating from the old GitHub app, **you must disable the "Create default resources" toggle** in the **Advanced Configuration** section. If you leave this toggle enabled, the integration will create new default blueprints and mappings that may conflict with your existing data, causing disruptions to your catalog. ![Create default resources toggle disabled in the Advanced Configuration](/img/integrations/github-ocean/HostedByPortCreateDefaultResourcesOff.png) By disabling this toggle, the integration will start up with empty mapping, which ensures that the integration won't disrupt existing entities. **For self-hosted deployments: set `createPortResourcesOrigin: Empty`** If you are deploying GitHub Ocean as self-hosted (Docker, Kubernetes, or CI), add the following to your values file or environment configuration during the migration: ``` createPortResourcesOrigin: Empty ``` This registers only the integration without creating default blueprints or mappings that could conflict with your existing data, just like the UI toggle for hosted deployments. You should remove this setting once all blueprints are successfully migrated. **Set `entityDeletionThreshold: 0` before running the migration** Before running `migrate`, add `entityDeletionThreshold: 0` to the GitHub Ocean integration's mapping config. The `migrate` command will refuse to run without it, as this prevents the next resync from deleting entities you just transferred while the new integration is still pointed at a temporary blueprint. You should remove the threshold flag once all blueprints are migrated and the new integration is pointed at the original blueprints. ``` deleteDependentEntities: true createMissingRelatedEntities: true entityDeletionThreshold: 0 # required during migration # ... rest of your mapping ... ``` ### GitOps managed config[​](#gitops-managed-config "Direct link to GitOps managed config") If you managed your integration mapping from a repository (using `port-app-config.yml`), you will need to perform an extra step during migration because the steps below require temporary mapping changes. To successfully complete the migration: 1. For each blueprint you migrate, copy **only the relevant `resources` block(s)** from `port-app-config.yml` into the GitHub Ocean mapping in Port. 2. Apply the migration steps below. 3. After you finish migrating all blueprints, copy the final mapping back into `port-app-config.yml` and re-enable repo-managed mapping. See [Managing your configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md#managing-your-configuration). ### Migrate each blueprint (entity ownership transfer)[​](#migrate-each-blueprint-entity-ownership-transfer "Direct link to Migrate each blueprint (entity ownership transfer)") **This is where you transfer ownership of entities from the old app to the new one.** For each blueprint, repeat these steps: **Step 1:** Create temporary blueprint * Duplicate your existing blueprint and rename to `-ocean-temp` (choose whatever name you want). **Step 2:** Map the new integration to the temporary blueprint ``` resources: - kind: repository port: entity: mappings: blueprint: '"githubRepository-ocean-temp"' # Use temp blueprint # ... rest of config ... ``` **Step 3:** Compare entities ``` port-github-migrator get-diff githubRepository githubRepository-ocean-temp \ --client-id \ --client-secret \ --old-installation-id 12345678 \ --new-installation-id github-ocean-v2 \ --port-url https://api.port.io # use https://api.us.port.io if you are using the US region # --ignore-property # optional: filter out dynamic properties from diff ``` Replace `12345678` with your numeric GitHub App installation ID and `github-ocean-v2` with the string identifier of your Port GitHub Ocean integration. **Step 4:** Adjust mappings if needed * Review the [kind mapping changes](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/migration/key-changes/.md) page and update your configuration if there are differences. **Step 5:** Transfer ownership (critical step) ``` port-github-migrator migrate githubRepository \ --client-id \ --client-secret \ --old-installation-id \ --new-installation-id \ --port-url https://api.port.io # use https://api.us.port.io if you are using the US region ``` This command transfers ownership of all entities in the blueprint from the old app to the new integration. It will refuse to run if `entityDeletionThreshold` is not set to `0` on the new integration (see [Set up the new integration](#set-up-the-new-integration)). **Step 6:** Update mapping and clean up * Point the new integration back to the original blueprint. * Delete the temporary blueprint. **Step 7:** Repeat for each remaining blueprint ### Migration tool reference[​](#migration-tool-reference "Direct link to Migration tool reference") This section documents migrator behavior that is good to know before you run it on large or unattended workloads. #### Per-blueprint batch limit[​](#per-blueprint-batch-limit "Direct link to Per-blueprint batch limit") `migrate ` and `get-diff ` operate on **up to 5000 entities per blueprint per invocation**. This cap exists because the underlying Port search API caps a single result set at the same number, and migrating in bounded batches keeps each run quick to retry and easy to reason about. What this means in practice: * If a blueprint has fewer than 5000 entities, the cap is invisible — everything migrates in one run. * If a blueprint has more than 5000 entities, `migrate` reports the cap up front (`Total entities affected: N / M (capped at 5000 per blueprint)`) and migrates the first 5000. Re-run `migrate ` to process the next batch. Each run picks up entities still owned by the legacy app, so progress is monotonic and safe to resume. * For continuous, unattended processing of every entity in a blueprint, use `--auto` (see below). It paginates past the 5000 cap and produces a single result file you can inspect afterwards. #### Auto mode (unattended bulk migration)[​](#auto-mode-unattended-bulk-migration "Direct link to Auto mode (unattended bulk migration)") `migrate --auto` paginates the source blueprint in batches of 5000, diffs each batch against the target blueprint under the new installation, patches identicals in place, and dumps the leftover entities (those that need attention) to a single JSON file under your local cache directory. It is designed for large blueprints and unattended runs (CI, scheduled jobs). Key differences from interactive `migrate`: * **No `'type yes'` confirmation prompt** — auto mode is meant to run unattended. * **Two positional arguments** — source and target blueprints, just like `get-diff`. The target is only used for diffing against the desired state; ownership is transferred on the source blueprint. * **No 5000 cap** — auto mode walks the search cursor until the source blueprint is exhausted. * **Result file** — every run writes `/auto//migration-result-.json` and prints its path on completion. The file contains: * `changed` — entities whose properties or relations differ between old and new, with the per-property diff so you can review what would change before re-running. * `notMigrated` — identifiers of entities that exist on the old side but have no counterpart under the new installation yet (typically because the new integration has not synced them, or the mapping does not produce them). * A live spinner reports the current operation per batch (`Batch N: fetching target entities…`, `Batch N: patching identicals…`, etc.) so progress is visible in CI logs. ``` port-github-migrator migrate githubRepository githubRepository-ocean-temp --auto \ --client-id \ --client-secret \ --old-installation-id \ --new-installation-id \ --port-url https://api.port.io \ # --ignore-property # optional: filter out dynamic properties from diff ``` After the run completes, inspect the result file. Entities listed under `notMigrated` typically need a resync of the new integration (or a mapping fix) before they can be migrated; entities under `changed` warrant a manual review of the property diffs before deciding whether to re-run `migrate` for that blueprint. #### Filtering dynamic properties with --ignore-property[​](#filtering-dynamic-properties-with---ignore-property "Direct link to Filtering dynamic properties with --ignore-property") Use `--ignore-property` with `get-diff` or `migrate --auto` to exclude specific properties from diffs. This is useful for filtering out dynamically-computed properties that differ between integrations but don't affect entity ownership transfer. ``` port-github-migrator get-diff githubRepository githubRepository-ocean-temp \ --client-id \ --client-secret \ --old-installation-id \ --new-installation-id \ --ignore-property updatedAt \ --ignore-property syncStatus ``` Specify `--ignore-property` once per property you want to exclude. #### Command flags reference[​](#command-flags-reference "Direct link to Command flags reference") | Flag | Description | Example | | ----------------------- | ---------------------------------------------------- | ------------------------------------------------- | | `--client-id` | Port API client ID | `(required)` | | `--client-secret` | Port API client secret | `(required)` | | `--old-installation-id` | Numeric GitHub App installation ID | `12345678` | | `--new-installation-id` | String Port integration identifier | `github-ocean-v2` | | `--port-url` | Port API endpoint | `https://api.port.io` or `https://api.us.port.io` | | `--auto` | Run unattended bulk migration with pagination | `migrate --auto` | | `--ignore-property` | Filter out dynamic properties from diff (repeatable) | `--ignore-property updatedAt` | #### Local state and result files[​](#local-state-and-result-files "Direct link to Local state and result files") The migrator persists a small amount of state under your user cache directory (resolved via Go's `os.UserCacheDir()` — typically `~/Library/Caches/port-github-migrator/` on macOS, `~/.cache/port-github-migrator/` on Linux, `%LocalAppData%\port-github-migrator\` on Windows): | Path | Written by | Purpose | | ------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `blueprints//.json` | `get-diff` and `migrate` | Identifier manifest with a 10-minute freshness window. `migrate` honors this manifest: it patches exactly the captured identifiers and removes the file on full success. | | `auto//migration-result-.json` | `migrate --auto` | One file per auto-mode run with the `changed` and `notMigrated` sections described above. These files are not removed automatically; they are intended as run artifacts. | You can safely delete anything under this directory at any time — `get-diff` will rebuild manifests on its next run, and `migrate` will fall back to a live search if no manifest is present. ### Update action backend types[​](#update-action-backend-types "Direct link to Update action backend types") If you have self-service actions or automations that use the legacy GitHub App backend (`type: GITHUB`), you must rewrite them to the GitHub Ocean backend (`type: INTEGRATION_ACTION`) before uninstalling the old integration. For each action or automation that uses GitHub workflows: 1. Navigate to the action's or automation's configuration in Port. 2. Update the backend from the legacy GitHub App backend to **GitHub workflow via Ocean**. 3. Set the `installationId` field to the name of your GitHub Ocean integration installation. 4. Configure the organization, repository, workflow, and integration action type (for example `dispatch_workflow`) as needed. Port does not auto-migrate `GITHUB` backends. Leftover legacy backends become unsupported after the October 15 cutover and fail closed when executed. For detailed configuration instructions, see the [GitHub Ocean backend documentation](/workflows/actions-and-automations/setup-backend/github-ocean/.md). ### Finalize and uninstall[​](#finalize-and-uninstall "Direct link to Finalize and uninstall") Only uninstall the legacy GitHub App **after:** * ✅ All blueprints are migrated. * ✅ Ownership is transferred for each blueprint (using the `migrate` command). * ✅ The new integration is syncing correctly. * ✅ All actions and automations use the GitHub Ocean backend. How you uninstall depends on how the legacy app was installed: | Install type | What to uninstall | | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | SaaS Port app (`getport-io` or `getport-io-us`) | Uninstall the Port GitHub App from your [GitHub installations](https://github.com/settings/installations) (or your organization's installations page). | | Custom or Enterprise GitHub App | Uninstall or delete **your** GitHub App under the GitHub organization settings. Do this in GitHub, not in the Port UI. | | Self-hosted Probot | Stop and remove the legacy container or deployment, then remove the old GitHub Enterprise app. Deploy [GitHub Ocean](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md) instead. | After October 15, 2026, Port keeps soft-deprecated legacy integration rows in Data Sources as read-only. Uninstalling in GitHub is still required on your side so failed webhook deliveries stop. ### Best practices[​](#best-practices "Direct link to Best practices") * **Start with non-critical blueprints** - Build confidence before migrating critical data. * **One blueprint at a time** - Easier to debug if issues arise. * **Use `get-diff` before migrating** - Verify entities match before transferring ownership. * **Keep the legacy app active until the checklist is done** - Removing it early leaves orphaned entities that you can only delete manually. * **Check Port logs** - Monitor for sync issues after migration. * **Finish before October 15, 2026** - After the cutover, legacy installs are read-only and legacy `GITHUB` action runs fail closed. --- # GitLab v2 ![GitLab icon](/img/guides/icons/GitLab.svg)![GitLab icon](/img/guides/icons/GitLab.svg) Loading version... Port's GitLab-v2 integration allows you to model GitLab resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Groups](https://docs.gitlab.com/ee/api/groups.html)[Groups With Members](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project)[Projects](https://docs.gitlab.com/ee/api/projects.html)[Projects With Members](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project)[Issues](https://docs.gitlab.com/ee/api/issues.html)[Merge Requests](https://docs.gitlab.com/ee/api/merge_requests.html)[Files](https://docs.gitlab.com/ee/api/repository_files.html)[Folders](https://docs.gitlab.com/ee/api/repositories.html#list-repository-tree)[Skills](https://docs.gitlab.com/ee/api/repositories.html#list-repository-tree)[Plugins](https://docs.gitlab.com/ee/api/repositories.html#list-repository-tree)[Members](https://docs.gitlab.com/ee/api/members.html)[Pipelines](https://docs.gitlab.com/ee/api/pipelines.html)[Jobs](https://docs.gitlab.com/ee/api/jobs.html)[Releases](https://docs.gitlab.com/ee/api/releases/index.html)[Tags](https://docs.gitlab.com/ee/api/tags.html)[Branches](https://docs.gitlab.com/ee/api/branches.html)[Deployments](https://docs.gitlab.com/ee/api/deployments.html) ## Core support [Live eventsSee supported resources](#set-up-live-events) GitLab v2 documentation This page documents the latest GitLab integration, released in April 2025. For documentation of the previous integration, check out the [GitLab](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md) page. For users upgrading from the v1 integration, see the [migration guide](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/migration/.md) to learn how to migrate to the new GitLab-v2 integration. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A GitLab account with permissions to create access tokens. * If you choose the real time & always on installation method, you will need a kubernetes cluster on which to install the integration. * Your Port user role is set to `Admin`. ### Configure access token[​](#configure-access-token "Direct link to Configure access token") To allow Port to fetch data from your GitLab instance, you need to create an access token. Port supports two types of tokens for authentication: personal access tokens and group access tokens. **Personal access token (PAT)** A Personal Access Token (PAT) is suitable if you're the only one managing the integration and don't need frequent credential rotation. To create a personal access token see the GitLab [personal access token guide](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html). * The token must belong to a user with access to the relevant GitLab resources (e.g., projects, groups). **Group access token** A Group Access Token is recommended when multiple team members manage the integration or when it's set up at the group level. To create a group access token, see the GitLab [group access token guide](https://docs.gitlab.com/ee/user/group/access_tokens.html). * Create the token in a group that has access to the relevant projects. * Set an appropriate expiration date and store it securely. **Service account token** A service account token is recommended where credentials must remain stable and unaffected by changes in human user membership. To set up the account and generate a personal access token, see the GitLab [service account guide](https://docs.gitlab.com/ee/user/profile/service_accounts/). * Add the service account to the relevant projects or groups with sufficient permissions (e.g., Developer or Maintainer). **Required scopes (click to expand)** **Legacy personal access tokens** * To enable **real-time updates using webhooks**, the token must include the `api` scope (required for managing webhooks). * If you're **not using realtime updates**, the token needs `read_api` and `read_repository` scopes. * To use the **trigger pipeline** or **create merge request** workflow actions, the `api` scope is required. **Fine-grained personal access tokens** Fine-grained tokens use resource-level permissions instead of broad scopes. The following permissions are required for the `trigger_pipeline` action: **Group and project** | Resource | Permission | | ---------------------- | ------------ | | Group | Read | | Project | Read | | Pipeline (CI/CD) | Create | | Webhook (Integrations) | Create, Read | **User** | Resource | Permission | | -------- | ---------- | | Project | Read | | Group | Read | The `create_merge_request` action requires one additional permission: **Group and project** | Resource | Permission | | ------------- | ---------- | | Merge request | Create | For the general catalog sync (without the trigger pipeline action), additional resource permissions for Merge Requests and other resource types are also required. ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party API into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true resources: - kind: project selector: query: 'true' includeLanguages: 'true' searchQueries: - name: hasPortConfig scope: blobs query: "filename:port.yml" includedFiles: - README.md - CODEOWNERS port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: url: .web_url readme: .__includedFiles["README.md"] codeowners: .__includedFiles["CODEOWNERS"] language: .__languages // {} | to_entries | max_by(.value) | .key hasPortConfig: .__searchQueries["hasPortConfig"] - kind: member selector: query: 'true' port: entity: mappings: identifier: .username title: .name blueprint: '"gitlabMember"' properties: url: .web_url state: .state email: .email locked: .locked - kind: group-with-members selector: query: 'true' port: entity: mappings: identifier: .full_path title: .name blueprint: '"gitlabGroup"' properties: url: .web_url visibility: .visibility description: .description relations: gitlabMembers: .__members | map(.username) - kind: merge-request selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabMergeRequest"' properties: creator: .author.name status: .state createdAt: .created_at updatedAt: .updated_at mergedAt: .merged_at link: .web_url leadTimeHours: >- (.created_at as $createdAt | .merged_at as $mergedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($mergedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $mergedTimestamp | if $mergedTimestamp == null then null else (((($mergedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) end) reviewers: .reviewers | map(.name) relations: project: .references.full // "" | gsub("!.+"; "") ``` ### Optional `project-with-members`[​](#optional-project-with-members "Direct link to optional-project-with-members") The default `port-app-config.yml` packaged with the integration does **not** include a **`project-with-members`** resource. To sync project members onto **`service`** entities, do the following: 1. In Port, add a **`gitlabMembers`** relation on the **`service`** blueprint (the default blueprint shipped with the integration does not define this relation on `service`). 2. In your **`port-app-config.yml`**, add a **`project-with-members`** resource that maps to **`service`** and uses `gitlabMembers: .__members | map(.username)` (see the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/examples/.md#mapping-projects-with-members) page for a sample block and selector reference). 3. Tune **`includeBotMembers`**, **`includeInheritedMembers`**, and **`includeOnlyActiveProjects`** as needed. If you enable **`includeInheritedMembers`**, expect **higher GitLab API usage and longer resyncs** than direct members only. The examples page goes into more detail. If you run **`project`** and **`project-with-members`** together, both target the same **`service`** identifier. The integration upserts each resource’s mapped fields using Port’s **create/update** flow. **Properties** coming from **`project`** and **relations** (for example **`gitlabMembers`**) coming from **`project-with-members`** can be **combined** on one entity as long as each mapping only sets the fields you intend—avoid duplicating the same property in both resources with different expressions, which can cause those fields to flip on successive syncs. For GitOps-driven fields from `port.yml`, see **`enableMergeEntity`** on the [Advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/advanced/.md#using-advanced-configurations) page (create/update vs create/override). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view the mapping and blueprint definition for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/examples/.md). ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 GitLab 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 GitLab 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If GitLab is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that GitLab can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Access control[​](#access-control "Direct link to Access control") The GitLab integration supports configurable access control to determine which resources are visible and accessible to the integration. You can configure access control using the `visibility` configuration block in the integration mapping. This allows you to filter resources based on GitLab access levels (Guest, Reporter, Developer, Maintainer, Owner). You can also disable access level filtering entirely to include all accessible resources. Access control configuration For detailed configuration options, access level reference, and practical examples, see the [Advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/advanced/.md#access-control) section. ## Capabilities[​](#capabilities "Direct link to Capabilities") For detailed information about data ingestion capabilities, see the [capabilities page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md). ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") Merge requests not appearing in catalog If you can see your GitLab repositories in Port but merge requests are missing, this is likely due to insufficient group-level permissions. **Symptoms** * GitLab projects/repositories are visible in Port. * Merge requests are missing from the catalog. * No error messages in integration logs. **Root cause** The GitLab V2 integration fetches merge requests using **group-level API queries**. If your integration token only has access to individual projects (e.g., Developer or Maintainer role at the project level), it cannot access the group-level merge request data. **Solution** Ensure your integration token has access to the **parent GitLab group** that contains your projects: * **For Personal Access Tokens**: The user must be a member of the group with at least Guest-level access. * **For Group Access Tokens**: Create the token in the parent group with appropriate scopes. * **For Service Account Tokens**: Add the service account to the group with at least Guest-level access. **Verification** To verify your token has the correct permissions: * Test the token against GitLab's group API: `GET /api/v4/groups/{group_id}/merge_requests`. * Ensure the token can list merge requests at the group level. * Check that the group is accessible with your current token permissions. ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/examples/.md) page for practical configurations and their corresponding blueprint definitions. ## GitOps[​](#gitops "Direct link to GitOps") Port's GitLab integration also provides GitOps capabilities, refer to the [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/gitops/.md) page to learn more. ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=GitLab). --- # Advanced The GitLab integration supports additional flags to provide extra configuration options, making it easier to customize its behavior to suit your needs. To use the advanced configuration and additional flags, let's add them as root keys to our integration configuration.
For example, here's how we can add the `createMissingRelatedEntities` flag: ``` createMissingRelatedEntities: false resources: - kind: merge-request selector: query: "true" port: entity: mappings: ... mappings configuration ``` ## Using advanced configurations[​](#using-advanced-configurations "Direct link to Using advanced configurations") Let's look at the advanced configuration parameters we can add to the [integration configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#configuration): * Delete dependent entities * Enable merge entity * Create missing related entities * Access control The `deleteDependentEntities` parameter is used to enable deletion of dependent Port entities. This is useful when you have two blueprints with a required relation, and the target entity in the relation should be deleted. In this scenario, the delete operation will fail if this flag is set to `false` if the flag is set to `true`, the source entity will be deleted as well. * **Default value**: `false` (disabled). * **Use case**: Enable this flag if dependent entities should be deleted when the target entity is deleted. With the `enableMergeEntity` parameter, you can specify whether to use the [create/update](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-update#usage) or [create/override](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-override#usage) strategy when creating entities listed in a `port.yml` file. * **Default value**: `false` (uses create/override). * **Use case**: Set to `true` to allow external sources to update some properties while GitLab remains the source of truth for others. The `createMissingRelatedEntities` parameter enables automatic creation of placeholder entities in Port when they're referenced in relationships but don't yet exist in your software catalog. * **Default value**: `true` to allow the GitLab app to create barebones related entities, in case those related entities do not exist in the software catalog. * **Use case**: use `false` if you do not want this default behavior (do not create missing related entities). The `visibility` configuration allows you to control which GitLab resources are accessible to the integration based on access levels. ### Access levels GitLab uses numeric access levels to define permissions: | Level | Role | Description | | ----- | ---------- | ------------------------------------ | | 10 | Guest | Read-only access to public resources | | 20 | Reporter | Can view and download code | | 30 | Developer | Can push code and manage issues | | 40 | Maintainer | Can manage project settings | | 50 | Owner | Full administrative access | ### Parameters * **`useMinAccessLevel`**: Boolean flag to enable/disable access level filtering * **Default value**: `true` * **Use case**: Set to `false` to include all accessible resources without filtering * **`minAccessLevel`**: Integer specifying the minimal required access level * **Default value**: `30` (Developer) * **Use case**: Restrict integration to resources where the token has the specified access level or higher ### Configuration examples ``` # Only sync owned projects visibility: useMinAccessLevel: true minAccessLevel: 50 resources: - kind: project selector: query: 'true' port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' ``` ``` # Include all accessible resources visibility: useMinAccessLevel: false resources: - kind: project selector: query: 'true' port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' ``` --- # Capabilities This page describes the data ingestion capabilities of Port's GitLab integration. ## Enrich entities with file contents[​](#enrich-entities-with-file-contents "Direct link to Enrich entities with file contents") You can enrich project, folder, and file entities with the contents of specific files from your repositories. This is useful for including README files, CODEOWNERS, or other documentation directly in your entity properties. To do this, use the `includedFiles` selector to specify which files to fetch, and then reference them in your mapping using `.__includedFiles[""]`. ``` resources: - kind: project selector: query: 'true' includedFiles: - README.md - CODEOWNERS port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: readme: .__includedFiles["README.md"] codeowners: .__includedFiles["CODEOWNERS"] url: .web_url ``` Deprecation notice The `file://` prefix in mappings is deprecated and will be removed in a future version. Use the `includedFiles` selector instead. The `file://` prefix will continue to work but will show deprecation warnings in the logs. The `includedFiles` feature: * Fetches file contents from the project's default branch (or specified branch for folder/file entities). * Stores file contents in the `__includedFiles` object in the raw data. * Handles missing files gracefully (stores `null` if a file doesn't exist). * Works with the **`project`** kind (and with folder/file kinds for their respective flows). The **`project-with-members`** kind does not support `includedFiles`. ## Enrich entities with search queries[​](#enrich-entities-with-search-queries "Direct link to Enrich entities with search queries") You can enrich project entities with search query results to check for the existence of files, patterns, or content in your repositories. This is useful for compliance checks, policy enforcement, and scorecard evaluations. To do this, use the `searchQueries` selector to specify which search queries to execute, and then reference the results in your mapping using `.__searchQueries[""]`. ``` resources: - kind: project selector: query: 'true' searchQueries: - name: hasPortConfig scope: blobs query: 'filename:port.yml' - name: hasCI scope: blobs query: 'filename:.gitlab-ci.yml' - name: hasDockerfile scope: blobs query: 'filename:Dockerfile' port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: hasPortConfig: .__searchQueries["hasPortConfig"] hasCI: .__searchQueries["hasCI"] hasDockerfile: .__searchQueries["hasDockerfile"] url: .web_url ``` The `searchQueries` feature: * Executes search queries using the [GitLab Advanced Search API](https://docs.gitlab.com/ee/api/search.html). * Stores results in the `__searchQueries` object in the raw data as boolean values (`true` if matches found, `false` otherwise). * Supports multiple search scopes (e.g., `blobs`, `commits`, `wiki_blobs`). * Handles errors gracefully (stores `null` if a query fails). * Applies to the **`project`** kind only, **`project-with-members`** does not support `searchQueries`. Each search query requires: * `name` - A unique name for this search query, used as the key in `__searchQueries`. * `scope` - The GitLab search scope (defaults to `blobs` if not specified). * `query` - The search query string following [GitLab Advanced Search syntax](https://docs.gitlab.com/ee/user/search/advanced_search.html#syntax). Deprecation notice The `search://` prefix in mappings is deprecated and will be removed in a future version. Use the `searchQueries` selector instead. The `search://` prefix will continue to work but will show deprecation warnings in the logs. ## Ingest files from your repositories[​](#ingest-files-from-your-repositories "Direct link to Ingest files from your repositories") Port allows you to fetch `JSON` and `YAML` files from your repositories, and create entities from them in your software catalog. This is done using the `file` kind in your GitLab mapping configuration. For example, say you want to manage your `package.json` files in Port. You will need to create a `manifest` blueprint, with each of its entities representing a `package.json` file. The following configuration fetches all `package.json` files from `my-project` and `my-other-project`, and creates an entity for each of them, based on the `manifest` blueprint: ``` resources: - kind: file selector: query: 'true' files: path: 'package.json' # Optional. Defaults to repositoryTree for complete path-based discovery. # Use groupSearch or projectSearch for faster exact-filename discovery via GitLab's Search API. # searchStrategy: repositoryTree repos: # Optional: omit to search across all accessible projects (repositoryTree) # or all top-most level groups (groupSearch/projectSearch). # Note: with groupSearch/projectSearch, repositories not part of any group are excluded # Replace with your repository's path_with_namespace (e.g., "group/project" or "group/subgroup/project") - group/my-project - group/my-other-project ``` ### Search strategy[​](#search-strategy "Direct link to Search strategy") The `files.searchStrategy` selector controls how the integration discovers files: | Strategy | How it works | Best for | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | | `repositoryTree` (default) | Walks each project's Git tree via the [repository tree API](https://docs.gitlab.com/ee/api/repositories.html#list-repository-tree). Does not depend on GitLab's search index. | Glob paths (`**/`, nested dirs), dot-directories, and complete, consistent results. | | `groupSearch` | Queries GitLab's [Advanced Search](https://docs.gitlab.com/ee/user/search/advanced_search.html) across accessible groups. | Faster discovery of exact filenames or simple `*` patterns when completeness is less critical. | | `projectSearch` | Queries Advanced Search per project. | Broad repository scans when group search is too coarse. | `groupSearch` and `projectSearch` rely on GitLab's Search API, which does not guarantee 100% completeness (indexing limits, file-size limits, and result-window caps). See GitLab's [advanced search known issues](https://docs.gitlab.com/user/search/advanced_search/#known-issues). When Advanced Search / blobs scope is unavailable for a group, `groupSearch` falls back to project-level search and caches that for the process lifetime - so results are not limited to Advanced Search alone. When to use `groupSearch` or `projectSearch` Use `groupSearch` or `projectSearch` when you need faster discovery of exact filenames or simple `*` patterns and can accept Search API completeness limits. Prefer the default `repositoryTree` for recursive globs (such as `infra/**/*.tf`), paths under `.cursor/` or `.claude/`, and when you need complete, consistent results. ``` resources: - kind: file selector: query: 'true' files: # Prefer a fixed prefix (e.g. `.cursor/skills/...`) so the tree walk is scoped. # repositoryTree is the default; you can omit searchStrategy. path: '.cursor/skills/**/SKILL.md' searchStrategy: repositoryTree repos: - group/my-skills-repo ``` For Agent Skills specifically, prefer the native [`skill` kind](#ingest-agent-skills) instead of `file` + `repositoryTree`. ### Path field capabilities[​](#path-field-capabilities "Direct link to Path field capabilities") Path syntax depends on the selected search strategy. #### `groupSearch` and `projectSearch`[​](#groupsearch-and-projectsearch "Direct link to groupsearch-and-projectsearch") These strategies use [GitLab's Advanced Search syntax](https://docs.gitlab.com/ee/user/search/advanced_search/#use-advanced-search), which differs from glob patterns. The search supports: * Simple `*` wildcards for filename patterns: `*.tf`, `package.json`, `test_*`. * Exact file paths: `src/app/main.py`, `infra/terraform/main.tf`. The search doesn't support: * Path wildcards like `infra/terraform/*.tf`. * Recursive patterns like `**/filename`. * Complex patterns like `*.{js,ts}`, `[abc]*`, and `!exclude`. For complex patterns, either switch to `repositoryTree` or split them into multiple file kinds with exact paths. #### `repositoryTree`[​](#repositorytree "Direct link to repositorytree") With `repositoryTree`, `path` is a **glob pattern** matched against repository tree paths. This supports: * Recursive globs: `**/package.json`, `infra/**/*.tf`. * Nested directories: `.cursor/skills/**/SKILL.md`. * Dot-directories that Advanced Search often under-indexes. The integration scopes each tree walk to the pattern's fixed path prefix (patterns that start with `**/` walk the full tree). The file kind still uses one `files.path` per resource; skill and plugin kinds share this tree matcher and can evaluate multiple patterns per repository in one walk. Test your mapping After adding the `file` kind to your mapping configuration, click on the `Resync` button. When you open the mapping configuration again, you will see real examples of files fetched from your GitLab organization. This will help you see what data is available to use in your `jq` expressions. Click on the **Test mapping** button to test your mapping against the example data. The structure of the available data is as follows: **Available data example (click to expand)** ``` { "file": { "file_name": "package.json", "file_path": "package.json", "size": 780, "encoding": "base64", "content_sha256": "d4dffc856dcacdaecb283ba9e47288beb6036d7c4ffff7c65f29057d890ecee9", "ref": "main", "blob_id": "7f8c2fea237a5cf0e1bcc17135c7c8b9e96edd49", "commit_id": "3ac75a99f6faa8ce4570368e6db1038c15f17cfc", "last_commit_id": "3ac75a99f6faa8ce4570368e6db1038c15f17cfc", "execute_filemode": false, "content": { "name": "my-awesome-project", "version": "1.0.0", "description": "A sample Node.js project", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1", "build": "echo \"Building the project...\"", "lint": "eslint ." }, "repository": { "type": "git", "url": "git+https://github.com/username/my-awesome-project.git" }, "keywords": [ "sample", "nodejs", "project" ], "author": "Your Name", "license": "ISC", "bugs": { "url": "https://github.com/username/my-awesome-project/issues" }, "homepage": "https://github.com/username/my-awesome-project#readme", "dependencies": { "@docsly/react": "^1.9.1", "@docusaurus/core": "^3.4.0", "@docusaurus/plugin-client-redirects": "^3.4.0", "@docusaurus/plugin-google-tag-manager": "^3.4.0", "@docusaurus/plugin-ideal-image": "^3.4.0", "@docusaurus/preset-classic": "^3.4.0", "@docusaurus/theme-live-codeblock": "^3.4.0", "@easyops-cn/docusaurus-search-local": "^0.44.2", "@mdx-js/react": "^3.0.1", "@port-labs/docusaurus-plugin-openapi-docs": "^0.0.5", "@port-labs/docusaurus-theme-openapi-docs": "^0.0.5", "@stackql/docusaurus-plugin-hubspot": "^1.0.1", "clsx": "^2.1.1", "docusaurus-plugin-hotjar": "^0.0.2", "docusaurus-plugin-image-zoom": "^2.0.0", "prettier": "^3.3.2", "prism-react-renderer": "^2.3.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-tooltip": "^5.27.1" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.4.0", "@docusaurus/tsconfig": "^3.4.0", "@docusaurus/types": "^3.4.0", "husky": "^9.0.11", "pretty-quick": "^4.0.0", "typescript": "~5.5.3" } } }, "repo": { "id": 60625101, "description": null, "name": "pages-test-project", "name_with_namespace": "port-labs / pages-test-project", "path": "pages-test-project", "path_with_namespace": "port-org/pages-test-project", "created_at": "2024-08-06T10:31:47.509Z", "default_branch": "main", "tag_list": [], "topics": [], "ssh_url_to_repo": "git@gitlab.com:port-org/pages-test-project.git", "http_url_to_repo": "https://gitlab.com/port-org/pages-test-project.git", "web_url": "https://gitlab.com/port-org/pages-test-project", "readme_url": "https://gitlab.com/port-org/pages-test-project/-/blob/main/README.md", "forks_count": 0, "avatar_url": null, "star_count": 0, "last_activity_at": "2024-08-14T15:40:56.606Z", "namespace": { "id": 66136652, "name": "port-labs", "path": "port-org", "kind": "group", "full_path": "port-org", "parent_id": null, "avatar_url": null, "web_url": "https://gitlab.com/groups/port-org" } } } ``` ### Create multiple entities from a single file[​](#create-multiple-entities-from-a-single-file "Direct link to Create multiple entities from a single file") In some cases, we want to parse a single JSON or YAML file and create multiple entities from it. To do this, we can use the `itemsToParse` key in our mapping configuration. For example, let's say we want to track or manage a project's dependencies in Port. We'll need to create a `package` blueprint, with each entity representing a dependency from a `package.json` file. The following configuration fetches a `package.json` file from a specific repository and creates an entity for each dependency in the file, based on the `package` blueprint: ``` resources: - kind: file selector: query: 'true' files: path: 'package.json' # Note that in this case we are fetching from a specific repository repos: - group/my-project port: itemsToParse: .file.content.dependencies | to_entries entity: mappings: # Since identifier cannot contain special characters, we are using jq to remove them identifier: >- .item.key + "_" + if (.item.value | startswith("^")) then .item.value[1:] else .item.value end title: .item.key + "@" + .item.value blueprint: '"package"' properties: package: .item.key version: .item.value relations: {} ``` The `itemsToParse` key is used to specify the path to the array of items you want to parse from the file. In this case, we are parsing the `dependencies` object from the `package.json` file. Once the object is parsed, we can use the `item` key to refer to each key-value pair within it — where the key is the dependency name, and the value is the version. This allows us to create an entity for each dependency dynamically. ### Multi-document YAML files[​](#multi-document-yaml-files "Direct link to Multi-document YAML files") For multi-document YAML files (a single file containing multiple YAML documents separated by `---`), `.file.content` will not resolve to an object, but to an array of objects. You can use one of these methods to ingest multi-document YAML files: 1. Use the `itemsToParse` key to create multiple entities from such a file (see example above). 2. Map the result to an `array` property. Mixed YAML types If you have both single-document and multi-document YAML files in your repositories, you can use the `itemsToParse` key like this to handle both cases: ``` itemsToParse: .file.content | if type== "object" then [.] else . end ``` ### Limitations[​](#limitations "Direct link to Limitations") * Currently only files up to 1MB in size are supported. * Only JSON and YAML formats are automatically parsed. Other file formats can be ingested as raw files, however, some special characters in the file (such as `\n`) may be processed and not preserved. * Currently only the default branch of the repository is supported. * With `groupSearch` or `projectSearch`, when you omit the `repos` field the integration searches across all top-most level groups you have access to. Repositories that are not part of any group are excluded. * With `repositoryTree`, discovery walks accessible projects via the GitLab projects API (optionally scoped with `repos`). Tree walks are slower than Advanced Search on large instances. For a list of known limitations with GitLab's Advanced Search, see GitLab's [Advanced Search documentation](https://docs.gitlab.com/ee/user/search/advanced_search.html#known-issues). For practical examples of using the `file` kind, see the [file kind examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/examples/.md#files-and-file-contents) page. ## Ingest agent skills[​](#ingest-agent-skills "Direct link to Ingest agent skills") Port can discover [Agent Skills](https://agentskills.io/specification) (`SKILL.md` files) from your GitLab projects using the dedicated `skill` kind. Each matched `SKILL.md` becomes one catalog entity with parsed frontmatter and markdown instructions. Skill discovery always walks the repository tree (same mechanism as `files.searchStrategy: repositoryTree`). Glob paths are supported. Tree walks are slower than Advanced Search on large instances. Without `paths[].repos`, the integration scans every accessible project — scope with `repos` when you can. Skill/plugin inherit `GroupSelector`; `includeOnlyActiveGroups` is mapped into project list params, so it filters active *projects* for tree walks despite the groups-oriented name. Default discovery paths (override with `selector.paths`): * `.agents/skills/**/SKILL.md` * `.agent/skills/**/SKILL.md` * `.cursor/skills/**/SKILL.md` * `.claude/skills/**/SKILL.md` * `.codex/skills/**/SKILL.md` * `.github/skills/**/SKILL.md` * `.opencode/skills/**/SKILL.md` * `skills/**/SKILL.md` ``` resources: - kind: skill selector: query: 'true' # Optional. Defaults to the paths listed above. # Each paths[] entry is independent: omitting repos on an entry # walks every accessible project for that pattern (the repos list # on another entry does not apply to it). paths: - path: '.cursor/skills/**/SKILL.md' repos: - group/my-skills-repo - path: 'skills/**/SKILL.md' repos: - group/my-skills-repo port: entity: mappings: identifier: .repo.path_with_namespace + "/" + .skill.skillMdPath title: .skill.name // .skill.skillMdPath blueprint: '"skill"' properties: description: .skill.description instructions: .skill.instructions path: .skill.skillMdPath root: .skill.root repo: .repo.path_with_namespace repoUrl: .repo.web_url branch: .__branch source: '"gitlab"' ``` Test your mapping After adding the `skill` kind and clicking **Resync**, open the mapping again to inspect live skill examples, then use **Test mapping** to validate your JQ. **Available data example (click to expand)** ``` { "skill": { "name": "hello-skill", "description": "A minimal example skill used to test skill discovery.", "instructions": "# Hello Skill\n\nRespond with a short greeting.\n", "frontmatter": { "name": "hello-skill", "description": "A minimal example skill used to test skill discovery." }, "path": "skills/hello-skill", "skillMdPath": "skills/hello-skill/SKILL.md", "root": "skills" }, "repo": { "id": 1, "name": "example-skills", "path_with_namespace": "acme/example-skills", "default_branch": "main", "web_url": "https://gitlab.com/acme/example-skills" }, "__branch": "main" } ``` Resync and push webhooks for skills both use the project's default branch. Branch deletes are ignored so catalog entities stay tied to default-branch content. The `skill` kind maps `SKILL.md` into instructions only. It does not ingest `references/` or `assets/`. If those files must land on the skill entity, use the `file` kind instead of combining kinds. For skills registry concepts, Port AI loading, and both mapping examples, see the [skills registry](/agent-management/ai-registry/skills/skills-registry/.md). ## Ingest agent plugins[​](#ingest-agent-plugins "Direct link to Ingest agent plugins") The `plugin` kind detects agent plugin packages in a repository (for example Claude, Cursor, Codex, OpenCode) and emits **one entity per repository**, merging the provider manifests that are present. Discovery uses the repository tree API (same as skills). Tree walks are slower than Advanced Search on large instances. Without `selector.repos`, the integration scans every accessible project — scope with `repos` when you can. Like skills, `includeOnlyActiveGroups` filters active projects for these tree walks. Configure which providers to detect with `selector.providers` (defaults to all supported providers). Optionally scope with `selector.repos` (`path_with_namespace` values). | Provider | Paths detected | | ------------- | --------------------------------------------------------------- | | `claude` | `.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` | | `cursor` | `.cursor-plugin/plugin.json` | | `codex` | `.codex-plugin/plugin.json` | | `agents` | `.agents/plugins/marketplace.json` | | `kimi` | `.kimi-plugin/plugin.json` | | `opencode` | files under `.opencode/plugins/` | | `pi` | files under `.pi/extensions/` | | `antigravity` | `gemini-extension.json` | ``` resources: - kind: plugin selector: query: 'true' providers: - claude - cursor - opencode # Optional project path_with_namespace filter repos: - group/my-plugin-repo port: entity: mappings: identifier: .repo.path_with_namespace title: .plugin.displayName // .plugin.name blueprint: '"agentPlugin"' properties: description: .plugin.description version: .plugin.version url: .repo.web_url repo: .repo.path_with_namespace source: '"gitlab"' supportsClaudeCode: .plugin.supports.claude supportsCursor: .plugin.supports.cursor supportsCodex: .plugin.supports.codex supportsAgents: .plugin.supports.agents supportsKimi: .plugin.supports.kimi supportsOpenCode: .plugin.supports.opencode supportsPi: .plugin.supports.pi supportsAntigravity: .plugin.supports.antigravity ``` **Available data example (click to expand)** ``` { "plugin": { "name": "superpowers", "displayName": "Superpowers", "description": "Core skills library", "version": "6.1.1", "supports": { "claude": true, "cursor": true, "codex": true, "agents": true, "kimi": true, "opencode": true, "pi": true, "antigravity": true }, "claude": { "name": "superpowers", "marketplaceName": "superpowers-dev" }, "cursor": { "name": "superpowers", "displayName": "Superpowers" }, "codex": { "name": "superpowers" }, "agents": { "name": "superpowers" }, "kimi": { "name": "superpowers" }, "opencode": { "detected": true }, "pi": { "detected": true }, "antigravity": { "name": "superpowers" } }, "repo": { "id": 2, "name": "superpowers", "path_with_namespace": "obra/superpowers", "default_branch": "main", "web_url": "https://gitlab.com/obra/superpowers" }, "__branch": "main" } ``` The raw payload always includes every provider key (`claude` … `antigravity`): a manifest object when present, `{ "detected": true }` for directory-only providers (`opencode` / `pi`), or `{}` when that provider is unsupported in the project. Resync and push webhooks for plugins both use the project's default branch. Branch deletes are ignored so catalog entities stay tied to default-branch content. --- # Examples This page contains mapping configuration examples for the GitLab integration. ### Groups[​](#groups "Direct link to Groups") The following example demonstrates how to ingest your GitLab groups, subgroups and projects to Port.
You can use the following Port blueprint definitions and integration configuration: **Group blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabGroup", "title": "Group", "icon": "GitLab", "schema": { "properties": { "visibility": { "icon": "Lock", "title": "Visibility", "type": "string", "enum": [ "public", "internal", "private" ], "enumColors": { "public": "red", "internal": "yellow", "private": "green" } }, "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link" }, "description": { "title": "Description", "type": "string", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Project blueprint (click to expand)** Create in Port ``` { "identifier": "service", "title": "Service", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "group": { "title": "Group", "target": "gitlabGroup", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: group selector: query: "true" port: entity: mappings: identifier: .full_path title: .name blueprint: '"gitlabGroup"' properties: url: .web_url visibility: .visibility description: .description - kind: project selector: query: "true" includeLanguages: "true" includedFiles: - README.md port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: url: .web_url readme: .__includedFiles["README.md"] description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch relations: group: >- .path_with_namespace | gsub(" "; "") | split("/") | .[:-1] | join("/") ``` #### Group configuration options[​](#group-configuration-options "Direct link to Group configuration options") Set `includeOnlyActiveGroups` to `true` to exclude groups that are archived or marked for deletion. By default, if not specified, all groups will be synced. ``` - kind: group selector: query: 'true' includeOnlyActiveGroups: true ``` ### Projects, README.md and merge requests[​](#projects-readmemd-and-merge-requests "Direct link to Projects, README.md and merge requests") Merge request access requirement Merge requests are fetched at the **group level**. Ensure your integration token has access to the parent GitLab group that contains your projects. Project-level access alone is not sufficient for merge request visibility. For more information, see the [troubleshooting](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#troubleshooting) section. The following example demonstrates how to ingest your GitLab projects, their README.md file contents and merge requests to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "service", "title": "Service", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Merge request blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabMergeRequest", "title": "Merge Request", "icon": "GitVersion", "schema": { "properties": { "creator": { "title": "Creator", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": [ "opened", "closed", "merged", "locked" ], "enumColors": { "opened": "yellow", "closed": "red", "merged": "green", "locked": "blue" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "mergedAt": { "title": "Merged At", "type": "string", "format": "date-time" }, "link": { "title": "Link", "format": "url", "type": "string" }, "leadTimeHours": { "title": "Lead Time in hours", "type": "number" }, "reviewers": { "title": "Reviewers", "type": "array", "items": { "type": "string" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Service", "target": "service", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` resources: - kind: project selector: query: "true" includeLanguages: "true" includedFiles: - README.md port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: url: .web_url readme: .__includedFiles["README.md"] description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch - kind: merge-request selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabMergeRequest"' properties: creator: .author.name status: .state createdAt: .created_at updatedAt: .updated_at mergedAt: .merged_at link: .web_url leadTimeHours: >- (.created_at as $createdAt | .merged_at as $mergedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($mergedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $mergedTimestamp | if $mergedTimestamp == null then null else (((($mergedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) end) reviewers: .reviewers | map(.name) relations: project: .references.full | gsub("!.+"; "") ``` #### Project configuration options[​](#project-configuration-options "Direct link to Project configuration options") Set `includeOnlyActiveProjects` to `true` to exclude projects that are archived or marked for deletion. By default, if not specified, all projects will be synced. ``` - kind: project selector: query: 'true' includeOnlyActiveProjects: true ``` #### Merge request configuration options[​](#merge-request-configuration-options "Direct link to Merge request configuration options") * States * Updated After * Include active groups The `states` selector allows you to filter merge requests based on their state. You can specify one or more states to include in the sync. Allowed values: * `opened`: Merge requests that are currently open. * `closed`: Merge requests that have been closed without merging. * `merged`: Merge requests that have been merged. By default, if not specified, only `opened` merge requests will be synced. ``` - kind: merge-request selector: query: 'true' states: - merged - opened ``` The `updatedAfter` selector allows you to filter merge requests based on when they were last updated. This helps you focus on recent changes and reduce the amount of historical data being synced. The value represents the number of days to look back for merge requests. For example, setting it to `90` will only sync merge requests that have been updated in the last 90 days. Merge request sync behavior The `updatedAfter` parameter only affects merge requests that are not in the "opened" state. Open merge requests will always be synced regardless of their last update time. By default, if not specified, it is set to `90` days. ``` - kind: merge-request selector: query: 'true' updatedAfter: 90 ``` Set `includeOnlyActiveGroups` to `true` to exclude merge requests whose parent group is archived or marked for deletion. By default, if not specified, all merge requests for the authorized groups will be synced. ``` - kind: merge-request selector: query: 'true' includeOnlyActiveGroups: true ``` #### Enrich merge request with review metrics[​](#enrich-merge-request-with-review-metrics "Direct link to Enrich merge request with review metrics") The merge request list does not include commit history or review activity. Enable `enrichWithCommits` and `enrichWithReviewDiscussion` on the `merge-request` selector to attach that data and calculate coding time, review time, and time from approval to merge. Both flags default to `false`. Each enabled flag adds GitLab API requests on resync and on live events. ``` - kind: merge-request selector: query: 'true' states: - opened - merged enrichWithCommits: true enrichWithReviewDiscussion: true ``` * `enrichWithCommits` attaches the commit list under `__commits`. * `enrichWithReviewDiscussion` attaches the notes list under `__notes`. Lead time (`leadTimeHours`) is creation to merge and does not need these flags. The other timings do: | Metric | Property | Definition | | ----------------- | ------------------------- | ----------------------------------------------------------------------- | | Coding time | `codingTimeHours` | Earliest commit to ready for review. | | Review time | `timeToFirstReview` | Creation to the first review comment, approval, or request for changes. | | Approval to merge | `timeFromApprovalToMerge` | Latest approval by someone other than the author, to merge. | GitLab's approvals API does not include an approval time. The mapping reads system notes with the body `approved this merge request` or `requested changes`, plus review comments where `system` is `false`. It ignores notes from the author, and administrative notes such as assignments and review requests. **Edit the merge request blueprint** The default `gitlabMergeRequest` blueprint does not include these properties. Edit the blueprint and add them before you use the mapping below: **Properties to add (click to expand)** ``` { "creatorUsername": { "title": "Creator Username", "type": "string" }, "isDraft": { "title": "Is Draft", "type": "boolean" }, "readyForReviewAt": { "title": "Ready For Review At", "type": "string", "format": "date-time", "description": "Falls back to createdAt when the merge request is not a draft. Uses the current draft state at sync time." }, "firstCommitAt": { "title": "First Commit At", "type": "string", "format": "date-time" }, "codingTimeHours": { "title": "Coding Time (Hours)", "type": "number" }, "firstReviewAt": { "title": "First Review At", "type": "string", "format": "date-time" }, "timeToFirstReview": { "title": "Time To First Review (Hours)", "type": "number" }, "approvedAt": { "title": "Approved At", "type": "string", "format": "date-time", "description": "Most recent approval from someone other than the merge request author." }, "timeFromApprovalToMerge": { "title": "Time From Approval To Merge (Hours)", "type": "number" } } ``` **Coding and review time mapping (click to expand)** ``` - kind: merge-request selector: query: 'true' states: - opened - merged enrichWithCommits: true enrichWithReviewDiscussion: true port: entity: mappings: identifier: .id | tostring blueprint: '"gitlabMergeRequest"' title: .title properties: creator: .author.name creatorUsername: .author.username status: .state createdAt: .created_at updatedAt: .updated_at mergedAt: .merged_at link: .web_url reviewers: .reviewers | map(.name) isDraft: .draft readyForReviewAt: (if .draft == true then null else .created_at end) leadTimeHours: >- (.created_at as $createdAt | .merged_at as $mergedAt | if $createdAt == null or $mergedAt == null then null else ((($mergedAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime)) / 3600 * 100 | floor) / 100 end) firstCommitAt: >- ([.__commits[]? | select(.authored_date != null)] | sort_by(.authored_date) | first.authored_date) // null codingTimeHours: >- (([.__commits[]? | select(.authored_date != null)] | sort_by(.authored_date) | first.authored_date) as $first | (if .draft == true then null else .created_at end) as $ready | if $first == null or $ready == null then null else ((($ready | sub("\\+00:00$"; "Z") | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($first | sub("\\+00:00$"; "Z") | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime)) / 3600 * 100 | floor) / 100 end) firstReviewAt: >- (.author.username as $author | [.__notes[]? | select(.author.username != $author) | select((.body | test("^(assigned to|requested review from|unassigned|removed review request)"; "i")) | not) | select((.system == false) or (.body | test("^approved this merge request$|^requested changes$"))) | .created_at ] | sort | first) // null timeToFirstReview: >- (.created_at as $created | (.author.username as $author | [.__notes[]? | select(.author.username != $author) | select((.body | test("^(assigned to|requested review from|unassigned|removed review request)"; "i")) | not) | select((.system == false) or (.body | test("^approved this merge request$|^requested changes$"))) | .created_at ] | sort | first) as $review | if $created == null or $review == null then null else ((($review | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($created | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime)) / 3600 * 100 | floor) / 100 end) approvedAt: >- (.author.username as $author | [.__notes[]? | select(.system == true) | select(.body | test("^approved this merge request$")) | select(.author.username != $author) | .created_at ] | sort | last) // null timeFromApprovalToMerge: >- ((.author.username as $author | [.__notes[]? | select(.system == true) | select(.body | test("^approved this merge request$")) | select(.author.username != $author) | .created_at ] | sort | last) as $approved | .merged_at as $merged | if $approved == null or $merged == null then null else ((($merged | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($approved | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime)) / 3600 * 100 | floor) / 100 end) relations: project: .references.full // "" | gsub("!.+"; "") ``` Timing limitations * `readyForReviewAt` uses the current draft state. A merge request opened as a draft and marked ready later is treated as ready at creation, which understates coding time and overstates review time. * Coding time is meaningful when your team opens draft merge requests. For a merge request opened directly, `readyForReviewAt` falls back to `createdAt`, so coding time is only the gap between the first commit and opening the merge request. * `approvedAt` excludes approvals by the merge request author. ### Projects and issues[​](#projects-and-issues "Direct link to Projects and issues") The following example demonstrates how to ingest your GitLab projects and their issues to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "service", "title": "Service", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Issue blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabIssue", "title": "Issue", "icon": "GitLab", "schema": { "properties": { "link": { "title": "Link", "type": "string", "format": "url" }, "description": { "title": "Description", "type": "string", "format": "markdown" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "closedAt": { "title": "Closed At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "creator": { "title": "Creator", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": ["opened", "closed"], "enumColors": { "opened": "green", "closed": "purple" } } } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Service", "target": "service", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: "true" includeLanguages: "true" port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: url: .web_url description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch - kind: issue selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabIssue"' properties: creator: .author.name status: .state createdAt: .created_at closedAt: .closed_at updatedAt: .updated_at description: .description link: .web_url labels: "[.labels[]]" relations: project: .references.full | gsub("#.+"; "") ``` #### Issues configuration options[​](#issues-configuration-options "Direct link to Issues configuration options") * State * Updated after * Include Active Groups * Issue Type * Labels * Include Non-Archived The `state` selector allows you to filter issues based on their state. You can specify one of the allowed values to include in the sync. Allowed values: * `opened`: Issues that are currently open. * `closed`: Issues that have been closed. By default, if not specified, all issues for the authorized groups will be synced. ``` - kind: issue selector: query: 'true' state: "closed" ``` The `updatedAfter` selector allows you to filter issues based on when they were last updated. This helps you focus on recent changes and reduce the amount of historical data being synced. The value represents the number of days to look back for issues. For example, setting it to `90` will only sync issues that have been updated on or after the last 90 days. Issue sync behavior The `updatedAfter` parameter only affects issues that are not in the "opened" state. Open issues will always be synced regardless of their last update time. By default, if not specified, all issues for the authorized groups will be synced. ``` - kind: issue selector: query: 'true' updatedAfter: 90 ``` Set `includeOnlyActiveGroups` to `true` to exclude issues whose parent group is archived or marked for deletion. By default, if not specified, all issues for the authorized groups will be synced. ``` - kind: issue selector: query: 'true' includeOnlyActiveGroups: true ``` The `issueType` selector allows you to filter issues based on their type. Allowed values: * `incident` * `issue` * `test_case` * `task` By default, if not specified, all issues for the authorized groups will be synced. ``` - kind: issue selector: query: 'true' issueType: "incident" ``` The `labels` selector allows you to filter issues based on a comma-separated list of labels. By default, if not specified, all issues for the authorized groups will be synced. Label filtering logic Issue must have **all labels** to be returned. ``` - kind: issue selector: query: 'true' labels: "dev,v1" ``` The `includeNonArchived` selector allows you to filter issues from non-archived projects. By default, if not specified, all issues for the authorized groups will be synced. ``` - kind: issue selector: query: 'true' nonArchived: true ``` ### Project and branches[​](#project-and-branches "Direct link to Project and branches") The following example demonstrates how to ingest your GitLab projects and their branches to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "service", "title": "Service", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Branch blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabBranch", "title": "Gitlab Branch", "icon": "GitLab", "schema": { "properties": { "default": { "type": "boolean", "title": "Default", "description": "Is default branch", "default": false }, "protected": { "type": "boolean", "title": "Protected", "default": false }, "last_commit_id": { "type": "string", "title": "Last Commit ID", "icon": "Git" }, "last_contributor": { "type": "string", "title": "Last Contributor", "icon": "User" }, "last_commit_date": { "type": "string", "format": "date-time", "title": "Last Commit Date", "dateFormat": "iso" }, "name": { "type": "string", "title": "Name" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Service", "target": "service", "required": true, "many": false } } } ``` **Integration Mapping (click to expand)** ``` resources: - kind: branch selector: query: 'true' defaultBranchOnly: true port: entity: mappings: identifier: .__project.path_with_namespace + "/" + .name | gsub(" "; "") title: .name blueprint: '"gitlabBranch"' properties: name: .name default: .default protected: .protected last_commit_id: .commit?.id last_contributor: .commit?.committer_email last_commit_date: .commit?.committed_date relations: project: .__project.path_with_namespace | gsub(" "; "") ``` #### Branch configuration options[​](#branch-configuration-options "Direct link to Branch configuration options") * Default branch only * Search * Regex * Include active projects When set to `true`, only the default branch of each repository is ingested. Defaults to `true`. ``` - kind: branch selector: query: 'true' defaultBranchOnly: true ``` The search selector ingests only branches whose names include the specified search string. Use `^term` or `term$` to match the start or end of a branch name. The following example ingests all branches starting with `test`: ``` - kind: branch selector: query: 'true' defaultBranchOnly: false search: ^test ``` The regex selector ingests only branches that match a specified [Re2](https://github.com/google/re2/wiki/Syntax) regular expression. The `regex` and `search` selectors cannot be used together - if both are specified, `regex` takes precedence. The following example ingests all branches starting with `test`: ``` - kind: branch selector: query: 'true' defaultBranchOnly: false regex: ^test ``` Set `includeOnlyActiveProjects` to `true` to exclude branches whose parent project is archived or marked for deletion. By default, branches from all projects are ingested regardless of project status. ``` - kind: branch selector: query: 'true' includeOnlyActiveProjects: true ``` ### Files and file contents[​](#files-and-file-contents "Direct link to Files and file contents") Below are two examples for mapping files and contents: The following example shows how to ingest dependencies from a `package.json` file in your GitLab repository into Port.
You can use the following Port blueprint definitions and integration configuration: **Package blueprint (click to expand)** Create in Port ``` { "identifier": "package", "title": "Package", "icon": "Package", "schema": { "properties": { "package": { "icon": "DefaultProperty", "type": "string", "title": "Package" }, "version": { "icon": "DefaultProperty", "type": "string", "title": "Version" } }, "required": [ "package", "version" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: file selector: query: 'true' files: # searchStrategy defaults to repositoryTree (glob patterns, e.g. **/package.json, infra/**/*.tf). # For GitLab Advanced Search syntax (exact paths / simple * wildcards), set # searchStrategy: groupSearch or projectSearch. path: package.json # searchStrategy: repositoryTree repos: # Replace with your repository's path_with_namespace (e.g., "group/project" or "group/subgroup/project") - group/my-project port: itemsToParse: .file.content.dependencies | to_entries entity: mappings: identifier: >- .item.key + "_" + if (.item.value | startswith("^")) then .item.value[1:] else .item.value end title: .item.key + "@" + .item.value blueprint: '"package"' properties: package: .item.key version: .item.value ``` The example will parse the `package.json` file in your repository and extract the dependencies into Port entities. *** The following example shows how to ingest teams from a YAML file in your GitLab repository into Port. **Team blueprint (click to expand)** Create in Port ``` { "identifier": "team", "title": "team", "icon": "Team", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "slack_channel_name": { "type": "string", "title": "Slack channel name" } }, "required": [ "description" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **YAML file example (click to expand)** ``` blueprint: team identifier: team title: development_team properties: description: Development team slack_channel_name: development-team ``` **Integration mapping (click to expand)** ``` resources: - kind: file selector: query: 'true' files: path: teams_file.yml repos: # Replace with your repository's path_with_namespace (e.g., "group/project" or "group/subgroup/project") - getport-labs/my-project port: entity: mappings: # Note that when parsing a YAML file, you should specify the index of the parsed document event when there is only one document. identifier: .file.content.[0].identifier title: .file.content.[0].title blueprint: .file.content.[0].blueprint properties: slack_channel_name: .file.content.[0].properties.slack_channel_name description: .file.content.[0].properties.description ``` The example will parse the YAML file in your repository and extract the teams into Port entities. For more information about ingesting files and file contents, see the [capabilities page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#ingest-files-from-your-repositories). #### File search strategy[​](#file-search-strategy "Direct link to File search strategy") The file kind defaults to `searchStrategy: repositoryTree`, which walks the repository tree and supports glob paths (including nested and dot-directories). You can omit `searchStrategy` or set it explicitly: ``` - kind: file selector: query: 'true' files: path: '.cursor/skills/**/SKILL.md' searchStrategy: repositoryTree repos: - group/my-skills-repo ``` To use GitLab's Search API instead (faster for exact filenames, but not guaranteed complete), set `searchStrategy: groupSearch` or `projectSearch`. See [Search strategy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#search-strategy) for when to use each option. ### Agent skills[​](#agent-skills "Direct link to Agent skills") The following example ingests Agent Skills (`SKILL.md`) from GitLab projects using the native `skill` kind. Discovery always uses the repository tree API. **Integration mapping (click to expand)** ``` resources: - kind: skill selector: query: 'true' paths: - path: '.cursor/skills/**/SKILL.md' # Optional: omit to scan all accessible projects repos: # Replace with your repository's path_with_namespace - group/my-skills-repo - path: 'skills/**/SKILL.md' port: entity: mappings: identifier: .repo.path_with_namespace + "/" + .skill.skillMdPath title: .skill.name // .skill.skillMdPath blueprint: '"skill"' properties: description: .skill.description instructions: .skill.instructions path: .skill.skillMdPath root: .skill.root repo: .repo.path_with_namespace repoUrl: .repo.web_url branch: .__branch source: '"gitlab"' ``` For more information, see [Ingest agent skills](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#ingest-agent-skills). ### Agent plugins[​](#agent-plugins "Direct link to Agent plugins") The following example detects agent plugin packages and creates one entity per repository: **Integration mapping (click to expand)** ``` resources: - kind: plugin selector: query: 'true' providers: - claude - cursor - opencode # Optional: omit to scan all accessible projects repos: # Replace with your repository's path_with_namespace - group/my-plugin-repo port: entity: mappings: identifier: .repo.path_with_namespace title: .plugin.displayName // .plugin.name blueprint: '"agentPlugin"' properties: description: .plugin.description version: .plugin.version url: .repo.web_url repo: .repo.path_with_namespace source: '"gitlab"' # Map every supports* flag you care about (capabilities maps all eight). supportsClaudeCode: .plugin.supports.claude supportsCursor: .plugin.supports.cursor supportsCodex: .plugin.supports.codex supportsAgents: .plugin.supports.agents supportsKimi: .plugin.supports.kimi supportsOpenCode: .plugin.supports.opencode supportsPi: .plugin.supports.pi supportsAntigravity: .plugin.supports.antigravity ``` For more information, see [Ingest agent plugins](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#ingest-agent-plugins). ### Projects and monorepos[​](#projects-and-monorepos "Direct link to Projects and monorepos") The following example demonstrates how to ingest your GitLab projects and their monorepo folders to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "service", "title": "Service", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: "apps/" # Relative path to the folders within the repositories. repos: # List of repositories to include folders from. - backend-service - frontend-service includedFiles: - README.md port: entity: mappings: identifier: .folder.name title: .folder.name blueprint: '"service"' properties: url: >- .repo.web_url + "/tree/" + .repo.default_branch + "/" + .folder.path description: .repo.description namespace: .repo.namespace.name full_path: .repo.path_with_namespace + "/" + folder.path language: .repo.__languages | to_entries | max_by(.value) | .key readme: .__includedFiles["README.md"] ``` To retrieve the root folders of your monorepo, you can use this following syntax in your `port-app-config.yml`: ``` - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: "/" # Relative path to the folders within the repositories repos: # List of repositories to include folders from. - backend-service - frontend-service ``` You can also specify a different path for each monorepo repository, for example: ``` - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: "apps/" repos: - gaming-apps - path: "microservices/" repos: - backend-services ``` ### Projects and folders[​](#projects-and-folders "Direct link to Projects and folders") The following example demonstrates how to ingest your GitLab projects and their folders to Port.
You can use the following Port blueprint definitions and integration configuration: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "service", "title": "Service", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Folder blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabFolder", "title": "Folder", "icon": "GitLab", "schema": { "properties": { "url": { "type": "string", "title": "URL", "format": "url", "description": "Link to the folder in GitLab" }, "readme": { "type": "string", "title": "README", "format": "markdown", "description": "Content of the folder's README file" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Service", "target": "service", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: "true" includeLanguages: "true" includedFiles: - README.md port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: url: .web_url readme: .__includedFiles["README.md"] description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch - kind: folder selector: query: "true" folders: - path: "/" # Using "/" will ingest the folders from the root of each of repository as entities repos: # Replace with your repository's path_with_namespace - group/backend-service - group/frontend-service includedFiles: - README.md port: entity: mappings: identifier: .folder.name title: .folder.name blueprint: '"gitlabFolder"' properties: url: >- .repo.web_url + "/tree/" + .repo.default_branch + "/" + .folder.path readme: .__includedFiles["README.md"] relations: project: .references.full | gsub("!.+"; "") ``` **Folder mapping patterns** You can specify different paths for different repositories: ``` resources: - kind: folder selector: query: "true" folders: - path: "apps/" repos: - group/gaming-services - path: "packages/" repos: - group/shared-libraries ``` ### Members and group with members[​](#members-and-group-with-members "Direct link to Members and group with members") #### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * When using **GitLab Self Hosted**, an admin token is required, rather than a group access token, to retrieve the `primary email addresses` of members. * When using **GitLab Enterprise**, accounts can retrieve the `primary email addresses` of members within their groups, provided the members are part of user accounts administered by an organization with [verified domains for groups](https://docs.gitlab.com/ee/user/enterprise_user/#verified-domains-for-groups). For more information, see [limitations](https://docs.gitlab.com/ee/api/members.html#limitations). GitLab free plan limitation Primary email addresses are not available for GitLab "Free plan" users. #### Members[​](#members "Direct link to Members") The following example demonstrates how to ingest your GitLab members to Port.
You can use the following Port blueprint definitions and integration configuration: **Member blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabMember", "title": "GitLab Member", "icon": "GitLab", "schema": { "properties": { "url": { "type": "string", "title": "URL", "format": "url", "description": "Link to the member's GitLab profile" }, "state": { "type": "string", "title": "State", "description": "The state of the member's account" }, "email": { "type": "string", "title": "Email", "format": "email", "description": "The member's email address" }, "created_by": { "type": "string", "title": "Created By", "description": "Name of the user who added this member" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration Mapping (click to expand)** ``` resources: - kind: member selector: query: 'true' port: entity: mappings: identifier: .username title: .name blueprint: '"gitlabMember"' properties: url: .web_url state: .state email: .email created_by: .created_by.name ``` To retrieve members only from groups that are **neither archived nor marked for deletion**, you can use the following syntax in your `port-app-config.yml`: ``` - kind: member selector: query: "true" includeOnlyActiveGroups: true ``` #### Groups with members[​](#groups-with-members "Direct link to Groups with members") The following example demonstrates how to ingest your GitLab groups and their members to Port.
You can use the following Port blueprint definitions and integration configuration: **Group with members blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabGroup", "title": "Group", "icon": "GitLab", "schema": { "properties": { "visibility": { "icon": "Lock", "title": "Visibility", "type": "string", "enum": [ "public", "internal", "private" ], "enumColors": { "public": "red", "internal": "yellow", "private": "green" } }, "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link" }, "description": { "title": "Description", "type": "string", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "gitlabMembers": { "title": "Members", "target": "gitlabMember", "required": false, "many": true } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: group-with-members selector: query: 'true' port: entity: mappings: identifier: .full_path title: .name blueprint: '"gitlabGroup"' properties: url: .web_url visibility: .visibility description: .description relations: gitlabMembers: .__members | map(.username) ``` * Include Bot Members * Include Inherited and Invited Members GitLab allows the creation of tokens (bots) for automated tasks, which can be associated with groups or projects via access tokens. The `includeBotMembers` parameter is used to filter out bot members from the actual GitLab members. By default, this selector is set to `false`, which means the integration will only sync actual members. ``` - kind: group-with-members selector: query: 'true' includeBotMembers: false ``` ``` - kind: members selector: query: 'true' includeBotMembers: false ``` You can also specify the `includeInheritedMembers` selector to control the inclusion of inherited and invited members in the member data. By default, this parameter is set to `false`, and the integration will sync only direct members without inherited members and/or invited members. Performance and GitLab API load Enabling `includeInheritedMembers` uses GitLab endpoints that return **all members, including inherited and invited membership**, not just direct members. That means **much larger payloads**, more work for GitLab, and **longer resyncs** and higher API volume. The slowdown can be several times worse than direct members only, depending on group depth and how many inherited or invited members you have. Keep this selector `false` unless you explicitly need inherited or invited members. Prefer validating on a **narrow** `selector.query` or a non-production GitLab scope first. For background on the API, see the [GitLab members API](https://docs.gitlab.com/ee/api/members.html). ``` - kind: group-with-members selector: query: 'true' includeInheritedMembers: true ``` ``` - kind: members selector: query: 'true' includeInheritedMembers: true ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * See [GitLab group and project member object structure](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project) in the GitLab API documentation. #### Mapping projects with members[​](#mapping-projects-with-members "Direct link to Mapping projects with members") Set up the blueprint and mapping on the [GitLab V2 configuration page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#optional-project-with-members) first. The tabs below cover **selectors** for this kind (`includeBotMembers`, `includeInheritedMembers`, `includeOnlyActiveProjects`). Each synced project includes a `__members` array with its direct (and optionally inherited) members. **Integration mapping (click to expand)** ``` resources: - kind: project-with-members selector: query: 'true' includeBotMembers: false # Use true only if you need inherited members; GitLab /members/all is much heavier than direct members. includeInheritedMembers: false port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: url: .web_url relations: gitlabMembers: .__members | map(.username) ``` * Include Bot Members * Include Inherited Members * Include Only Active Projects GitLab allows the creation of tokens (bots) for automated tasks, which can be associated with groups or projects via access tokens. The `includeBotMembers` parameter is used to filter out bot members from the actual GitLab members. By default, this selector is set to `false`, which means the integration will only sync actual members. ``` - kind: project-with-members selector: query: 'true' includeBotMembers: false ``` You can specify the `includeInheritedMembers` selector to control the inclusion of inherited members in the member data. By default, this parameter is set to `false`, and the integration will use `/projects/:id/members` to sync only direct members. When set to `true`, the integration uses `/projects/:id/members/all` to include inherited members from parent groups. Performance and GitLab API load The `/projects/:id/members/all` path returns **every effective member** on the project (including membership inherited from parent groups), so it is **much heavier** than direct members only. Expect **higher GitLab load**, larger `__members` arrays, and **longer resyncs** (often several times longer on large hierarchies, depending on inherited membership). Enable this only when you truly need inherited members. Start with a **narrow** `selector.query` or a small set of projects and measure duration before rolling out broadly. This mirrors the cost profile of `includeInheritedMembers` on `group-with-members`, which keeps behavior aligned between group-level and project-level membership in the integration. ``` - kind: project-with-members selector: query: 'true' includeInheritedMembers: true ``` Set `includeOnlyActiveProjects` to `true` to exclude projects that are archived or marked for deletion. By default, this parameter is set to `null`, which means all projects are included. ``` - kind: project-with-members selector: query: 'true' includeOnlyActiveProjects: true ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * See [GitLab group and project member object structure](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project) in the GitLab API documentation. ### Projects, pipelines and jobs[​](#projects-pipelines-and-jobs "Direct link to Projects, pipelines and jobs") The following example demonstrates how to ingest your GitLab projects, their pipelines and jobs runs to Port.
You can use the following Port blueprint definitions and integration configuration: **Pipeline blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabPipeline", "title": "GitLab Pipeline", "icon": "GitLab", "schema": { "properties": { "creator": { "type": "string", "title": "Creator", "description": "Name of the user who triggered the pipeline" }, "status": { "type": "string", "title": "Status", "description": "Current status of the pipeline" }, "createdAt": { "type": "string", "title": "Created At", "format": "date-time", "description": "When the pipeline was created" }, "updatedAt": { "type": "string", "title": "Updated At", "format": "date-time", "description": "When the pipeline was last updated" }, "description": { "type": "string", "title": "Description", "description": "Pipeline description" }, "link": { "type": "string", "title": "Link", "format": "url", "description": "URL to the pipeline in GitLab" } }, "required": [] }, "relations": { "project": { "title": "Service", "target": "service", "required": false, "many": false } } } ``` **Job blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabJob", "title": "GitLab Job", "icon": "GitLab", "schema": { "properties": { "creator": { "type": "string", "title": "Creator", "description": "Name of the user who triggered the job" }, "startedAt": { "type": "string", "title": "Started At", "format": "date-time", "description": "When the job started" }, "updatedAt": { "type": "string", "title": "Updated At", "format": "date-time", "description": "When the job was last updated" }, "finishedAt": { "type": "string", "title": "Finished At", "format": "date-time", "description": "When the job finished" }, "stage": { "type": "string", "title": "Stage", "description": "Pipeline stage this job belongs to" }, "status": { "type": "string", "title": "Status", "description": "Current status of the job" }, "link": { "type": "string", "title": "Link", "format": "url", "description": "URL to the job in GitLab" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "pipeline": { "title": "Pipeline", "target": "gitlabPipeline", "required": false, "many": false } } } ``` **Integration Mapping (click to expand)** ``` resources: - kind: pipeline selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .title // (.id | tostring) blueprint: '"gitlabPipeline"' properties: creator: .user.name status: .status createdAt: .created_at updatedAt: .updated_at description: .description link: .web_url relations: project: .__project.path_with_namespace - kind: job selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"gitlabJob"' properties: creator: .user.name startedAt: .started_at updatedAt: .updated_at finishedAt: .finished_at stage: .stage status: .status link: .web_url relations: pipeline: .pipeline.id | tostring ``` To retrieve pipelines only from projects that are **neither archived nor marked for deletion**, you can use the following syntax in your `port-app-config.yml`: ``` - kind: pipeline selector: query: "true" includeOnlyActiveProjects: true ``` To retrieve jobs only from projects that are **neither archived nor marked for deletion**, you can use the following syntax in your `port-app-config.yml`: ``` - kind: job selector: query: "true" includeOnlyActiveProjects: true ``` #### Pipeline and job known issues[​](#pipeline-and-job-known-issues "Direct link to Pipeline and job known issues") Known issues with pipeline sync The following issues may cause pipelines to sync incorrectly or be silently excluded. Review your configuration if you notice missing pipeline data. **Date format mismatch causing jq errors (click to expand)** **Symptom:** Pipelines fail to sync or cause jq errors during processing. **Cause:** GitLab returns `created_at` timestamps in the format `YYYY-MM-DDTHH:MM:SS.XXX-XX:XX` (with milliseconds and timezone offset), but some jq date selectors expect the format `%Y-%m-%dT%H:%M:%SZ` (without milliseconds, with Z suffix). **Workaround:** If you're using a custom jq selector that filters by date, normalize the date format before comparison: ``` (.created_at | sub("\\.\\d+[+-]\\d{2}:\\d{2}$"; "Z") | fromdateiso8601) > (now - 2592000) ``` This expression: 1. Removes the milliseconds and timezone offset (`.XXX-XX:XX`). 2. Appends `Z` for UTC format. 3. Compares against the current time minus 30 days (2592000 seconds). **Default branch filter excludes non-standard branches (click to expand)** **Symptom:** Pipelines from branches other than `main`, `master`, or `production` are not synced. **Cause:** Some default configurations include a `.selector.query` that filters pipelines to only those running on specific branches: ``` selector: query: '.ref == "main" or .ref == "master" or .ref == "production"' ``` Pipelines running on feature branches, release branches, or any other branch names will be silently excluded. **Fix:** Update your selector query to include all branches or specify the branches you want: ``` # Include all branches selector: query: "true" # Or include specific additional branches selector: query: '.ref == "main" or .ref == "master" or .ref == "production" or .ref == "develop" or (.ref | startswith("release/"))' ``` ### Releases[​](#releases "Direct link to Releases") The following example demonstrates how to ingest your GitLab releases to Port.
You can use the following Port blueprint definitions and integration configuration: **Release blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRelease", "title": "GitLab Release", "icon": "GitVersion", "schema": { "properties": { "tagName": { "title": "Tag Name", "type": "string" }, "name": { "title": "Name", "type": "string" }, "description": { "title": "Description", "type": "string", "format": "markdown" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "releasedAt": { "title": "Released At", "type": "string", "format": "date-time" }, "author": { "title": "Author", "type": "string" }, "commitSha": { "title": "Commit SHA", "type": "string" }, "upcomingRelease": { "title": "Upcoming Release", "type": "boolean" } } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "service": { "title": "Service", "target": "service", "required": false, "many": false }, "gitlabMember": { "title": "Author", "target": "gitlabMember", "required": false, "many": false } } } ``` **Integration Mapping (click to expand)** ``` resources: - kind: release selector: query: 'true' port: entity: mappings: identifier: .__project.path_with_namespace + "/" + .tag_name | gsub(" "; "") title: .name blueprint: '"gitlabRelease"' properties: tagName: .tag_name name: .name description: .description createdAt: .created_at releasedAt: .released_at author: .author.name commitSha: .commit.id upcomingRelease: .upcoming_release relations: service: .__project.path_with_namespace | gsub(" "; "") gitlabMember: .author.username ``` To retrieve releases only from projects that are **neither archived nor marked for deletion**, you can use the following syntax in your `port-app-config.yml`: ``` - kind: release selector: query: "true" includeOnlyActiveProjects: true ``` ### Deployments[​](#deployments "Direct link to Deployments") The `deployment` kind imports GitLab CI/CD deployments into Port. Each deployment includes data from its parent project, making project fields available in mappings. When the `pipeline` kind is enabled, deployments are automatically linked to their corresponding `gitlabPipeline` entities. To reduce noise, sync only successful production deployments by filtering on `status` and `environment`: ``` selector: query: 'true' apiQueryParams: status: success environment: production ``` **Deployment blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabDeployment", "title": "GitLab Deployment", "icon": "GitLab", "schema": { "properties": { "status": { "title": "Status", "type": "string", "description": "Current deployment status. Note: 'skipped' may appear on deployments in practice but is not a valid apiQueryParams.status filter value.", "enum": [ "created", "running", "success", "failed", "canceled", "blocked", "skipped" ], "enumColors": { "created": "blue", "running": "yellow", "success": "green", "failed": "red", "canceled": "darkGray", "blocked": "orange", "skipped": "lightGray" } }, "ref": { "title": "Ref", "type": "string", "description": "Branch or tag name that was deployed." }, "sha": { "title": "Commit SHA", "type": "string", "description": "SHA of the commit that was deployed." }, "environment": { "title": "Environment", "type": "string", "description": "Name of the environment the deployment targets (e.g. production, staging)." }, "environmentUrl": { "title": "Environment URL", "type": "string", "format": "url", "description": "External URL of the target environment." }, "triggeredBy": { "title": "Triggered By", "type": "string", "description": "GitLab username of the user who triggered the deployment." }, "jobName": { "title": "Job Name", "type": "string", "description": "Name of the CI/CD job that executed the deployment. Null for API-triggered deployments." }, "commitTitle": { "title": "Commit Message", "type": "string", "description": "Title line of the commit message. Null for API-triggered deployments." }, "commitAuthor": { "title": "Commit Author", "type": "string", "description": "Author name of the commit. Null for API-triggered deployments." }, "pipelineUrl": { "title": "Pipeline URL", "type": "string", "format": "url", "description": "URL of the pipeline that triggered this deployment. Null for API-triggered deployments." }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "startedAt": { "title": "Started At", "type": "string", "format": "date-time", "description": "When the CI job started executing. Null for API-triggered deployments." }, "finishedAt": { "title": "Finished At", "type": "string", "format": "date-time", "description": "When the CI job finished. Use this field for DORA Deployment Frequency calculations. Null for API-triggered deployments." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "service": { "title": "Service", "target": "service", "required": false, "many": false }, "pipeline": { "title": "Pipeline", "target": "gitlabPipeline", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` - kind: deployment selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: '.environment.name + " #" + (.iid | tostring)' blueprint: '"gitlabDeployment"' properties: status: .status ref: .ref sha: .sha environment: .environment.name environmentUrl: .environment.external_url // null triggeredBy: .user.username jobName: .deployable.name // null commitTitle: .deployable.commit.title // null commitAuthor: .deployable.commit.author_name // null pipelineUrl: .deployable.pipeline.web_url // null createdAt: .created_at updatedAt: .updated_at startedAt: .deployable.started_at // null finishedAt: .deployable.finished_at // null relations: service: .__project.path_with_namespace | gsub(" "; "") pipeline: .deployable.pipeline.id | if . != null then tostring else null end ``` #### Deployment selector options[​](#deployment-selector-options "Direct link to Deployment selector options") * Sync Window * Filter by environment * Filter by status * Finished-at window Limit which deployments are synced into Port by specifying a time window. **Note:** Port reconciliation will delete entities outside this window on every resync - these filters control what is synced, not how frequently it syncs. ``` - kind: deployment selector: query: 'true' apiQueryParams: updatedAfter: "2026-06-01T00:00:00Z" ``` Scope deployments to a specific environment, for example to track only production deployments. ``` - kind: deployment selector: query: 'true' apiQueryParams: environment: production ``` Filter deployments by their current status. Valid filter values are `created`, `running`, `success`, `failed`, `canceled`, and `blocked`. ``` - kind: deployment selector: query: 'true' apiQueryParams: status: success ``` Return only deployments whose CI job finished within a time window. Requires `status: success` per the GitLab API constraint. ``` - kind: deployment selector: query: 'true' apiQueryParams: status: success finishedAfter: "2026-06-01T00:00:00Z" finishedBefore: "2026-06-13T00:00:00Z" ``` ### Tags[​](#tags "Direct link to Tags") The following example demonstrates how to ingest your GitLab tags to Port.
You can use the following Port blueprint definitions and integration configuration: **Tag blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabTag", "title": "GitLab Tag", "icon": "GitVersion", "schema": { "properties": { "tagName": { "title": "Tag Name", "type": "string" }, "message": { "title": "Message", "type": "string" }, "protected": { "title": "Protected", "type": "boolean" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "commitSha": { "title": "Commit SHA", "type": "string" }, "commitTitle": { "title": "Commit Title", "type": "string" } } }, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "service": { "title": "Service", "target": "service", "required": false, "many": false } } } ``` **Integration Mapping (click to expand)** ``` resources: - kind: tag selector: query: 'true' port: entity: mappings: identifier: .__project.path_with_namespace + "/" + .name | gsub(" "; "") title: .name blueprint: '"gitlabTag"' properties: tagName: .name message: .message protected: .protected createdAt: .created_at commitSha: .commit.id commitTitle: .commit.title relations: service: .__project.path_with_namespace | gsub(" "; "") ``` To retrieve tags only from projects that are **neither archived nor marked for deletion**, you can use the following syntax in your `port-app-config.yml`: ``` - kind: tag selector: query: "true" includeOnlyActiveProjects: true ``` --- # GitOps Port's GitLab integration makes it possible to manage Port entities with a GitOps approach, making your code projects into the source of truth for the various infrastructure assets you want to manage. baseUrl parameter When [installing the Gitlab integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#setup), the `liveEvents.baseUrl` parameter must be provided in order to use GitOps.
Without it, the integration will not be able to receive webhook events about pushes to the `port.yml` file. ## Common use cases[​](#common-use-cases "Direct link to Common use cases") * Use GitLab as the source-of-truth for your **microservices**, **projects**, **packages**, **libraries** and other software catalog assets. * Allow developers to keep the catalog up-to-date, by making updates to files in their Git projects. * Create a standardized way to document software catalog assets in your organization. ## Managing entities using GitOps[​](#managing-entities-using-gitops "Direct link to Managing entities using GitOps") To manage entities using GitOps, you will need to add a `port.yml` file to the **default branch** (usually `main`) of your project. The `port.yml` file can specify one or more Port entities that will be ingested to Port, and any change made to the `port.yml` file will also be reflected inside Port. This configuration turns your GitLab projects to the source-of-truth for the software catalog. ### GitOps `port.yml` file[​](#gitops-portyml-file "Direct link to gitops-portyml-file") The `port.yml` file is how you specify your Port entities that are managed using GitOps and whose data is ingested from your Git projects. Here are examples for valid `port.yml` files: * Single entity * Multiple entities ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ``` - identifier: myEntity1 title: My Entity1 blueprint: myFirstBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 - identifier: myEntity title: My Entity2 blueprint: mySecondBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ``` Since both of the valid `port.yml` formats follow the same structure, the following section will explain the format based on the single entity example. ### `port.yml` structure[​](#portyml-structure "Direct link to portyml-structure") Here is an example `port.yml` file: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` * The `identifier` key is used to specify the identifier of the entity that the app will create and keep up-to-date when changes occur: ``` identifier: myEntity title: My Entity ... ``` * The `title` key is used to specify the title of the entity: ``` identifier: myEntity title: My Entity ... ``` * The `blueprint` key is used to specify the identifier of the blueprint to create this entity from: ``` ... title: My Entity blueprint: myBlueprint ... ``` * The `properties` key is used to map the values to the different properties of the entity: ``` ... title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ... ``` * The `relations` key is used to map target entities to the different relations of the entity: * Single relation * Many relation ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity ``` ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ### Setting null properties[​](#setting-null-properties "Direct link to Setting null properties") When you want to clear a property's value in Port, you can explicitly set it to `null` in your `port.yml` file.
For example: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: description: null owner: null ``` Difference between null and omitting When you omit a property entirely from the `port.yml`, Port will keep its existing value. Setting a property to `null` explicitly tells Port to clear that property's value. ### Ingesting project file contents[​](#ingesting-project-file-contents "Direct link to Ingesting project file contents") It is possible to use the contents of files in the repository as the value for entity properties using a simple reference. The following example will read the string contents of `~/module1/README.md` and upload it to `myStringProp` of the specified entity. Repository folder structure used for the example: ``` root | +- port.yml | +-+ module1 | | | +- README.md | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: myStringProp: file://module1/README.md ``` #### Using relative paths[​](#using-relative-paths "Direct link to Using relative paths") It is also possible to use paths relative to the location of the `port.yml` spec file. For example: `file://./` is used to reference a file in the same directory as the `port.yml` file. `file://../` is used to reference a file that is one directory above and so on. The following example reads `README.md` and `module1/requirements.txt` using paths relative to `port.yml` Repository folder structure used for the example: ``` root | +-+ meta | | | +-- port.yml | | | +-+ README.md | +-+ module1 | | | +- requirements.txt | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: readme: file://./README.md module1Requirements: file://../module1/requirements.txt ``` ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Port.yml ingestion pattern[​](#portyml-ingestion-pattern "Direct link to Port.yml ingestion pattern") The `port.yml` ingestion pattern allows you to configure the GitLab integration to ingest `port.yml` files as part of the resync process. This approach is particularly useful when you want to maintain data integrity and ensure that your `port.yml` files are properly synchronized with Port. `port.yml` files operate in the GitOps methodology, meaning they are ingested into Port whenever a commit to the main branch of the repository is detected. Here's how to configure the integration to parse and ingest `port.yml` files: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: entity: mappings: identifier: .file.content.identifier title: .file.content.title blueprint: .file.content.blueprint properties: property_a: .file.content.properties.property_a property_b: .file.content.properties.property_b relations: relation_a: .file.content.relations.relation_a relation_b: .file.content.relations.relation_b ``` For multiple entities in a single `port.yml` file, use the `itemsToParse` key: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: itemsToParse: .file.content entity: mappings: identifier: .item.identifier title: .item.title blueprint: .item.blueprint properties: property_a: .item.properties.property_a property_b: .item.properties.property_b relations: relation_a: .item.relations.relation_a relation_b: .item.relations.relation_b ``` ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/advanced/.md) page for advanced use cases and configurations. --- # Mapping extensions ## Overview[​](#overview "Direct link to Overview") The default way to map your data to Port is by using [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform your data to Port entities. However, in some cases you may want to map data to Port in a way that default JQ mapping is not enough. Common use cases: * Map your repository README.md file contents into Port. * Check if a specific file exists in your repository. * Check if a specific string exists in your repository. * Check if a specific version of a package is used in your repository. * Check if a CI/CD pipeline is configured in your repository. ## Mapping file content into Port[​](#mapping-file-content-into-port "Direct link to Mapping file content into Port") The following example demonstrates how to define and export your GitLab projects and their **README.md** file contents to Port: **Project blueprint (click to expand)** Create in Port ``` { "identifier": "service", "title": "Service", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` As we can see one of the properties is of type markdown, this means that we need to map the **README.md** file contents into Port. To do so, we will use the `includedFiles` selector to specify which files to fetch, and then reference them in the mapping using `.__includedFiles[""]`. ``` - kind: project selector: query: "true" includedFiles: - README.md port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: url: .web_url readme: .__includedFiles["README.md"] description: .description namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch ``` Deprecation notice The `file://` prefix is deprecated and will be removed in a future version. Use the `includedFiles` selector instead.
The `file://` prefix will continue to work but will show deprecation warnings in the logs. ## Search checks[​](#search-checks "Direct link to Search checks") We can use the GitLab exporter to perform search checks on our repositories to ensure that they are compliant with our organization's policies and standards. With Port Scorecards and the search checks feature, organization can define a set of checks that will be performed on each repository, and the results of those checks will be reflected in the repository's scorecard in Port which will help the organization to identify repositories that are not compliant with the organization's policies and standards. ### How it works[​](#how-it-works "Direct link to How it works") The search checks are performed using the [GitLab Advanced Search API](https://docs.gitlab.com/ee/api/search.html). This means that any search query supported by the GitLab Search API can be used with the GitLab exporter. ### Using searchQueries selector[​](#using-searchqueries-selector "Direct link to Using searchQueries selector") To perform search checks, use the `searchQueries` selector to specify which search queries to execute, and then reference the results in your mapping using `.__searchQueries[""]`. Each search query in the `searchQueries` list should have: * `name` - A unique name for this search query, used as the key in `__searchQueries`. * `scope` - The GitLab search scope (e.g., `blobs`, `commits`, `wiki_blobs`, etc.). Defaults to `blobs` if not specified. * `query` - The search query string (e.g., `filename:port.yml`). The results are stored under `__searchQueries[]` as a boolean (`true` if matches found, `false` otherwise). ### Search checks examples[​](#search-checks-examples "Direct link to Search checks examples") * Check whether the project contains a `README.md` md file: `filename:README.md`. * Check whether the project contains tests: `filename:test_* extension:py`. * Check whether the project has GitLab CI configured: `filename:.gitlab-ci.yml`. * Check whether the project uses fastapi in the `requirements.txt` file: `fastapi filename:requirements.txt`. In the screenshot below we can see that we use the exact same query as GitLab advanced search ![GitLab advanced search query results](/img/integrations/gitlab/GitlabSearchQueryExample.png) ### Integration config example[​](#integration-config-example "Direct link to Integration config example") ``` - kind: project selector: query: 'true' includedFiles: - README.md searchQueries: - name: hasLicense scope: blobs query: 'filename:"LICENSE"' - name: usingFastapiPackage scope: blobs query: 'fastapi filename:requirements.txt' - name: hasCI scope: blobs query: 'filename:.gitlab-ci.yml' - name: usingOldLoggingPackage scope: blobs query: 'logging extension:py' - name: hasTests scope: blobs query: 'filename:test_* extension:py' port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: url: .web_link description: .description namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch readme: .__includedFiles["README.md"] hasLicense: .__searchQueries["hasLicense"] usingFastapiPackage: .__searchQueries["usingFastapiPackage"] hasCI: .__searchQueries["hasCI"] usingOldLoggingPackage: .__searchQueries["usingOldLoggingPackage"] hasTests: .__searchQueries["hasTests"] ``` Deprecation notice The `search://` prefix is deprecated and will be removed in a future version. Use the `searchQueries` selector instead.
The `search://` prefix will continue to work but will show deprecation warnings in the logs. The new approach provides better performance and clearer configuration, as all search queries are defined in one place in the selector. Example of a Scorecard that reflects the results of the search checks: ![Best App Code Maturity scorecard passing README, CI, and test checks](/assets/images/GitlabSearchScorecardExample-a7e03a6eb54bcdc9d55e31fdf9e94c36.png) --- # Migration guide to the v2 integration This guide walks through the process of migrating from Port's GitLab integration to the new GitLab-v2 integration. For installation methods, live events, and where to paste YAML in Port, use the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#setup) and [configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#configuration) sections on the GitLab v2 integration page. ## Overview[​](#overview "Direct link to Overview") GitLab-v2 introduces significant authentication and performance improvements: * **Simplified authentication**: Replace multiple group tokens with a single service account token. * **Enhanced performance**: Faster resync with improved API efficiency. * **Typed configuration**: First-class selectors for kinds such as `merge-request`, `member`, `group-with-members`, `project-with-members`, `tag`, `release`, and `branch`. * **Native search and file selectors**: Use `searchQueries` and `includedFiles` on `project` instead of the deprecated `file://` and `search://` mapping prefixes. For how those selectors work in mappings, see [Enrich entities with file contents](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#enrich-entities-with-file-contents) and [Enrich entities with search queries](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#enrich-entities-with-search-queries) on the capabilities page. ## Changes[​](#changes "Direct link to Changes") ### Authentication model[​](#authentication-model "Direct link to Authentication model") **GitLab approach**: Multiple group access tokens mapped to specific paths ``` integration: secrets: tokenMapping: | {"glpat-token1": ["**/Group1/**"], "glpat-token2": ["**/Group2/**"]} ``` **GitLab-v2 approach**: Single service account token with broad access ``` integration: config: gitlabToken: "glpat-service-account-token" ``` Parameter naming The GitLab-v2 integration uses `gitlabToken` (not `token`). When using the hosted UI or Helm installer, use the parameter name exactly as it appears in the integration spec. Parameter reference and install flows live under [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#setup) on the GitLab v2 integration page. ### Blueprint mapping changes[​](#blueprint-mapping-changes "Direct link to Blueprint mapping changes") The blueprints used in GitLab-v2 have evolved to provide cleaner data structures and better relationships between entities. Understanding these changes is essential for a successful migration. Fields that Port adds on top of the GitLab API payload use a double underscore prefix (for example `.__languages` and `.__includedFiles`), similar to other Ocean integrations. For feature-level behavior beyond this migration page, see the [capabilities page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md). In the YAML below, **`identifier`**, **`title`**, and **`blueprint`** are standard Port entity mapping fields (they are not GitLab integration switches): * **`identifier`**: stable unique key for the entity in your catalog (often derived from GitLab IDs or paths). * **`title`**: human-readable label shown in the UI. * **`blueprint`**: slug of the Port blueprint that defines the entity's schema and relations. Those three lines stay the same **when you already map GitLab projects or MRs into the same blueprints as before**. What changes in each subsection are **selectors** (what GitLab sends into the integration) and **properties** (what you store on the entity). #### Kind mapping changes by resource type[​](#kind-mapping-changes-by-resource-type "Direct link to Kind mapping changes by resource type") ##### 1. **project** kind (Required update)[​](#1-project-kind-required-update "Direct link to 1-project-kind-required-update") **GitLab configuration:** ``` - kind: project selector: query: "true" includeLabels: "true" # ❌ Remove: No longer supported port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: language: .__languages | to_entries | max_by(.value) | .key labels: .__labels # ❌ Remove: project labels not enriched in v2 ``` **GitLab-v2 configuration:** ``` - kind: project selector: query: "true" includeLanguages: "true" # ✅ Add: New selector for language detection port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: language: .__languages | to_entries | max_by(.value) | .key ``` Why project labels went away GitLab-v2 **does not** offer `includeLabels` or a project-level `.__labels` field. The v1 integration optionally fetched extra project label data into the payload. That enrichment path was removed to simplify the **project** sync and reduce API surface. **Issues**, **merge requests**, and other kinds can still expose **`labels`** where the underlying GitLab object includes them (see the **issue** example later on this page). If your scorecards or queries depended on **project** labels, move that signal to a supported surface, for example MR/issue labels, [`includedFiles`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#enrich-entities-with-file-contents), or [`searchQueries`](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#enrich-entities-with-search-queries) on **`project`**. ##### 2. **merge-request** kind (Configuration update)[​](#2-merge-request-kind-configuration-update "Direct link to 2-merge-request-kind-configuration-update") **GitLab configuration:** ``` - kind: merge-request port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabMergeRequest"' ``` **GitLab-v2 configuration:** ``` - kind: merge-request selector: query: "true" states: ["opened", "merged"] # ✅ Add: control which MR states are synced updatedAfter: 90 # ✅ Add: only applies when querying non-open states (days) port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabMergeRequest"' properties: creator: .author.name reviewers: .reviewers | map(.name) ``` Merge request parity The default merge-request behavior is **not** equivalent between v1 and v2: * **v1** fetched all `opened` MRs **and** `closed` MRs from roughly the last 14 days. * **v2** defaults to `states: ["opened"]` only. The `updatedAfter` value (default 90 days) is applied only when you also include non-open states (`merged`, `closed`, `locked`). To preserve v1's catalog coverage, explicitly set `states` to include `merged` and/or `closed` and tune `updatedAfter` to your retention needs. ##### 3. **Member handling changes** (Recommended modeling change)[​](#3-member-handling-changes-recommended-modeling-change "Direct link to 3-member-handling-changes-recommended-modeling-change") Both v1 and v2 support `project-with-members` and `group-with-members`. The recommended modeling in v2, however, is to use a dedicated `member` kind and reference it via relations, rather than embedding members as properties on projects and groups. In the selectors below, **`includeVerboseMemberObject`** only changed how verbose each member object was in the raw payload. GitLab-v2 uses a single standardized member shape, so that toggle is gone. It has nothing to do with **`blueprint`** (which only selects which Port blueprint receives the entity). **GitLab approach - Members as embedded properties:** ``` - kind: project-with-members selector: query: 'true' includeInheritedMembers: false includeBotMembers: false includeVerboseMemberObject: false # ❌ Remove: not supported in v2 port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"service"' properties: members: .__members # Members embedded as properties - kind: group-with-members selector: query: 'true' includeInheritedMembers: false includeBotMembers: false includeVerboseMemberObject: false # ❌ Remove: not supported in v2 port: entity: mappings: identifier: .full_path title: .name blueprint: '"gitlabGroup"' properties: members: .__members # Members embedded as properties ``` **GitLab-v2 recommended approach - Members as separate entities with relations:** ``` # ✅ New: Dedicated member entities - kind: member selector: query: 'true' includeBotMembers: false port: entity: mappings: identifier: .username title: .name blueprint: '"gitlabMember"' properties: url: .web_url state: .state email: .email locked: .locked # ✅ Updated: Groups with member relations (not embedded properties) - kind: group-with-members selector: query: 'true' includeBotMembers: false port: entity: mappings: identifier: .full_path title: .name blueprint: '"gitlabGroup"' properties: url: .web_url visibility: .visibility description: .description relations: gitlabMembers: .__members | map(.username) # ✅ Relations, not properties ``` ##### 4. **file** kind (Search API and repository format changes)[​](#4-file-kind-search-api-and-repository-format-changes "Direct link to 4-file-kind-search-api-and-repository-format-changes") For file resync, GitLab-v2 now uses GitLab's Advanced Search API with its specific syntax and limitations: **GitLab format:** ``` - kind: file selector: files: path: '**/package.json' # ❌ Custom glob patterns repos: - "backend-service" # ❌ Simple repository name - "frontend-app" ``` **GitLab-v2 format:** ``` - kind: file selector: files: path: 'package.json' # ✅ GitLab Advanced Search syntax (https://docs.gitlab.com/user/search/advanced_search/#use-advanced-search) repos: - "my-org/backend-service" # ✅ Full namespace/project path - "my-org/frontend-app" skipParsing: false # ✅ New option to skip parsing and return raw content ``` Path search limitations GitLab-v2's `path` parameter uses [GitLab's Advanced Search syntax](https://docs.gitlab.com/user/search/advanced_search/#use-advanced-search), which differs from v1's glob patterns. The search supports simple `*` wildcards for filename patterns like `*.tf`, `package.json`, and `test_*`, as well as exact file paths like `src/app/main.py` and `infra/terraform/main.tf`. The search doesn't support path wildcards like `infra/terraform/*.tf` ❌, recursive patterns like `**/filename` ❌, or complex patterns like `*.{js,ts}`, `[abc]*`, and `!exclude` ❌. When migrating complex v1 patterns, you can split them into multiple file kinds or specify exact paths for targeted files. See the [Migration Pattern Comparison](#migration-pattern-comparison) table below for specific examples. ##### 5. **folder** kind (Repository selector changes)[​](#5-folder-kind-repository-selector-changes "Direct link to 5-folder-kind-repository-selector-changes") For folder resync, GitLab-v2 changes the repository specification format to support branch-specific folder mapping: **GitLab format:** ``` - kind: folder selector: folders: - path: "src" repos: - "backend-service" # ❌ Simple repository name - "frontend-app" branch: "main" # ❌ Single branch for all repos ``` **GitLab-v2 format:** ``` - kind: folder selector: folders: - path: "src" repos: - name: "my-org/backend-service" # ✅ Full namespace/project path branch: "main" # ✅ Branch specified per repository - name: "my-org/frontend-app" branch: "develop" # ✅ Different branches per repository ``` ##### 6. **First-class kinds with typed selectors in GitLab-v2**[​](#6-first-class-kinds-with-typed-selectors-in-gitlab-v2 "Direct link to 6-first-class-kinds-with-typed-selectors-in-gitlab-v2") `pipeline`, `job`, and `issue` were already syncable in v1 through the integration's resync logic, but v2 promotes them (along with `group`, `tag`, `release`, and `branch`) to first-class kinds with typed selectors in `port-app-config.yml`. This means cleaner configuration, validation, and consistent defaults rather than hardcoded behavior. **pipeline** kind: ``` - kind: pipeline selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"gitlabPipeline"' ``` **job** kind: ``` - kind: job selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"gitlabJob"' properties: status: .status stage: .stage duration: .duration ``` **issue** kind: ``` - kind: issue selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabIssue"' properties: creator: .author.name status: .state labels: "[.labels[]]" relations: project: .references.full | gsub("#.+"; "") ``` ##### 7. **Additional first-class kinds: `group`, `tag`, `release`, `branch`**[​](#7-additional-first-class-kinds-group-tag-release-branch "Direct link to 7-additional-first-class-kinds-group-tag-release-branch") GitLab-v2's `GitlabPortAppConfig` exposes typed selectors for `group`, `tag`, `release`, and `branch`. If you tracked any of these in v1 via custom mappings, you can now use the dedicated kinds for cleaner configuration. See the [GitLab v2 examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/examples/.md) for usage, and [Ingest files from your repositories](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#ingest-files-from-your-repositories) when you rely on the `file` kind. ##### 8. **Visibility and minimum access level filters**[​](#8-visibility-and-minimum-access-level-filters "Direct link to 8-visibility-and-minimum-access-level-filters") GitLab-v2 introduces a top-level `visibility` configuration on the integration's app config to limit which projects and groups are synced based on the requesting user's access level: ``` visibility: useMinAccessLevel: true minAccessLevel: 30 # Developer ``` Use this to scope ingestion to projects/groups where the service account has at least a chosen access level. ##### 9. **Replacing `file://` and `search://` mapping prefixes**[​](#9-replacing-file-and-search-mapping-prefixes "Direct link to 9-replacing-file-and-search-mapping-prefixes") Both v1 and v2 deprecate the `file://` and `search://` prefixes inside Port mappings. v2 provides first-class selectors on the `project` kind to replace them: * **`includedFiles`**: declaratively pull file contents into project entities (replaces `file://` lookups in mappings). * **`searchQueries`**: declaratively run GitLab Advanced Search queries (replaces `search://` lookups). Example: ``` - kind: project selector: query: "true" includedFiles: - path: "package.json" repos: - "my-org/backend-service" searchQueries: - name: "todos" query: "TODO" ``` When you migrate, replace any remaining `file://` or `search://` references in your mappings with these selectors. Read [Enrich entities with file contents](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#enrich-entities-with-file-contents) and [Enrich entities with search queries](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#enrich-entities-with-search-queries) for concrete YAML patterns. ### Configuration selector changes[​](#configuration-selector-changes "Direct link to Configuration selector changes") GitLab-v2 introduces new selector options while removing some GitLab-specific configurations that are no longer needed. #### Removed selectors[​](#removed-selectors "Direct link to Removed selectors") The following selectors from GitLab are no longer available in GitLab-v2: * **includeLabels**: optional v1 enrichment that pulled GitLab **project** labels into `.__labels` for mapping. GitLab-v2 drops this path entirely (see the **Why project labels went away** callout under **project** kind on this page). Use **`includeLanguages`** when you want primary language on **project** entities instead. * **includeVerboseMemberObject**: v1-only switch for the shape of member objects on member-related kinds. GitLab-v2 always uses one canonical member shape, so the flag was removed. #### New selectors in GitLab-v2[​](#new-selectors-in-gitlab-v2 "Direct link to New selectors in GitLab-v2") GitLab-v2 introduces several new selector options that provide better control over data resync: * **includeLanguages**: Enables language detection and inclusion in project entities. When set to true, the integration will analyze repository languages and include the primary language in the project properties * **states**: Available for merge requests, allows you to specify which merge request states to resync (opened, closed, merged) * **updatedAfter**: For merge requests, helps limit resync to recently updated merge requests by specifying the number of days to look back ### File and folder mapping updates[​](#file-and-folder-mapping-updates "Direct link to File and folder mapping updates") The way you specify repositories for file and folder resync has changed significantly between versions. | Repository Specification | GitLab Format | GitLab-v2 Format | | ------------------------ | ------------------- | -------------------------- | | File repos | `"backend-service"` | `"my-org/backend-service"` | | Folder repos | `"frontend-app"` | `"my-org/frontend-app"` | The namespace path format follows GitLab's standard `group/project` or `group/subgroup/project` structure, ensuring accurate repository identification even when multiple projects share the same name across different groups. Short repo names on file and folder kinds For **`file`** and **`folder`** selectors (and similar searches scoped by repo list), treat **`path_with_namespace`** style paths as **required** in GitLab-v2. Short names such as `"backend-service"` were tied to how the legacy integration resolved repos and **do not** behave as reliable aliases in v2. Keeping the old strings usually breaks ingestion or targets the wrong GitLab project once you have more than one project with the same name under different groups. This change applies to **file/folder** configuration specifically, not to fields such as **`identifier`** that still use `.path_with_namespace` from the **project** object. ## Summary of key changes[​](#summary-of-key-changes "Direct link to Summary of key changes") The following table summarizes all the major differences between GitLab and GitLab-v2 to help you prepare for migration: | Kind/Feature | GitLab | GitLab-v2 | Action Required | | ------------------------------------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | **Authentication** | `tokenMapping` (multi-token, path-scoped) | Single `gitlabToken` | Replace token mapping with a single service account token | | **project** kind | `includeLabels` selector | `includeLanguages` selector | Replace `includeLabels: true` with `includeLanguages: true` | | **merge-request** kind | Defaults to opened + \~14 days of closed MRs | Defaults to `opened` only; `updatedAfter` (default 90d) only applies for non-open states | Add non-open states explicitly and tune `updatedAfter` to match v1 coverage | | **Member handling** | `project-with-members` + `group-with-members` (embedded) | Recommended: dedicated `member` kind with relations | Optional: introduce `member` kind and switch groups/projects to relations | | **`includeVerboseMemberObject`** | ✅ Supported | ❌ Removed | Remove from member/project/group selectors | | **file** kind | Custom glob patterns + simple repo names | GitLab Advanced Search syntax + full namespace paths + `skipParsing` option | Update to GitLab search syntax, update repo names to `org/repo` format | | **folder** kind | Simple repo names + single branch | Repository objects with name/branch pairs | Update repos to objects with `name` and `branch` properties | | **`file://` / `search://` prefixes** | Deprecated | Replaced by `includedFiles` and `searchQueries` on `project` | Migrate mapping prefixes to selectors | | **pipeline / job / issue** | Available via integration resync | Promoted to first-class kinds with typed selectors | Use the typed selectors in `port-app-config.yml` | | **group / tag / release / branch** | Custom mappings | First-class kinds with typed selectors | Optional: switch to dedicated kinds | ## Migration steps[​](#migration-steps "Direct link to Migration steps") ### Step 1: Create a GitLab service account[​](#step-1-create-a-gitlab-service-account "Direct link to Step 1: Create a GitLab service account") Choose the appropriate setup method for your GitLab instance: **For GitLab.com (SaaS):** 1. Navigate to your top-level group settings 2. Go to **Access Tokens** under the group settings 3. Create a **Group Service Account** **For GitLab self-managed:** 1. Create a new user account (e.g., `port-integration-bot`) 2. Add this user to all relevant groups with **Developer** permissions or higher ### Step 2: Generate the access token[​](#step-2-generate-the-access-token "Direct link to Step 2: Generate the access token") 1. Sign in to your service account 2. Navigate to **User Settings > Access Tokens** 3. Create a new personal access token with: * **Name**: `Port Integration Token` * **Scopes**: `api` (or `read_api` + `read_repository` for read-only) * **Expiration**: Set according to your security policy 4. Save the generated token securely Token permissions The service account needs access to all groups and projects you want to sync. For webhook functionality, it also needs permission to create webhooks. ### Step 3: Update your integration configuration[​](#step-3-update-your-integration-configuration "Direct link to Step 3: Update your integration configuration") Replace your current token mapping configuration with the new single token approach. This step eliminates the complexity of managing multiple tokens while ensuring the service account has appropriate access across your GitLab instance. When you are ready to paste the full mapping into Port, use the YAML editor described under [configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#configuration) on the GitLab v2 integration page. **Before: GitLab configuration with token mapping (click to expand)** ``` integration: secrets: tokenMapping: | { "glpat-old-token-1": ["**/DevTeam/**", "**/BackendServices/**"], "glpat-old-token-2": ["**/FrontendApps/**"] } ``` **After: GitLab-v2 configuration with single token (click to expand)** ``` integration: config: gitlabToken: "glpat-your-new-service-account-token" ``` ### When your mapping lives in a repository[​](#when-your-mapping-lives-in-a-repository "Direct link to When your mapping lives in a repository") If you keep the integration mapping in a repository (for example `port-app-config.yml`), you will need short-lived edits in the Port UI while you validate selector changes. For each pass, copy only the relevant `resources` blocks into the mapping in Port, run the steps below, then copy the final YAML back to the repository. The [configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#configuration) section on the GitLab v2 integration page explains how the mapping is stored and synced. ### Step 4: Update resource selectors and blueprints[​](#step-4-update-resource-selectors-and-blueprints "Direct link to Step 4: Update resource selectors and blueprints") Review your mapping configuration for deprecated selectors and blueprint references. Cross-check the examples under [Blueprint mapping changes](#blueprint-mapping-changes) and the [Summary of key changes](#summary-of-key-changes) table before you deploy. **Update each kind mapping specifically:** 1. **For `project` kinds:** * Remove `includeLabels: true` from selectors * Add `includeLanguages: true` to selectors * Remove `labels: .__labels` from property mappings 2. **For `merge-request` kinds:** * To preserve v1's coverage of closed MRs, add states such as `states: ["opened", "merged", "closed"]`. Without this, v2 syncs `opened` only. * Tune `updatedAfter` (in days, default 90) to bound how far back closed/merged MRs are fetched. v1's behavior was roughly a 14-day window. * Simplify reviewer mappings to `reviewers: .reviewers | map(.name)`. 3. **For member handling (recommended modeling change):** * Optional: introduce a dedicated `member` kind and switch groups/projects to use relations instead of embedded properties. * You can keep `project-with-members` and `group-with-members` if you prefer to migrate without changing your modeling, both are still supported in v2. * Remove the deprecated `includeVerboseMemberObject` selector. `includeInheritedMembers` and `includeBotMembers` remain supported. 4. **For `file` kinds:** * Update file paths to use GitLab Advanced Search syntax (change `'**/package.json'` to `'*package.json'`) * Update all repo specifications from `"repo-name"` to `"org/repo-name"` format * Optionally add `skipParsing: false` to selector if you want to control content parsing 5. **For `folder` kinds:** * Update all repo specifications from simple strings to objects with `name` and `branch` properties * Change from `repos: ["repo-name"]` to `repos: [{"name": "org/repo-name", "branch": "main"}]` * Take advantage of per-repository branch specification for more flexible folder mapping 6. **Adopt first-class kinds where helpful:** * Use the typed `pipeline`, `job`, and `issue` kinds for CI/CD and issue tracking. * Use the typed `group`, `tag`, `release`, and `branch` kinds if you previously approximated these with custom mappings. * Replace any remaining `file://` and `search://` mapping prefixes with `includedFiles` and `searchQueries` selectors on the `project` kind. ### Step 5: Update file and folder mappings[​](#step-5-update-file-and-folder-mappings "Direct link to Step 5: Update file and folder mappings") Update any file or folder mappings to use GitLab's Advanced Search API syntax and repository formats required by GitLab-v2. For selector semantics and limitations, see [Ingest files from your repositories](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/capabilities/.md#ingest-files-from-your-repositories) on the capabilities page. 1. **Update file paths to GitLab Advanced Search syntax:** * Change `path: '**/package.json'` to `path: 'package.json'` * Change `path: '**/*.yml'` to `path: '*.yml'` * GitLab-v2 uses [GitLab's Advanced Search API](https://docs.gitlab.com/user/search/advanced_search/#use-advanced-search) which supports `*` wildcards but follows GitLab's search syntax 2. **Update repository specifications:** * Change repository names to include the full group path * Review all `repos` specifications in your file and folder kinds 3. **Test the updated configuration with a small subset first** **Example file kind transformation:** ``` # GitLab format - kind: file selector: files: path: '**/package.json' # ❌ Custom glob pattern repos: - "backend-service" # ❌ Simple name # GitLab-v2 format - kind: file selector: files: path: '*package.json' # ✅ GitLab Advanced Search syntax repos: - "my-org/backend-service" # ✅ Full namespace skipParsing: false # ✅ Optional parsing control ``` **Example folder kind transformation:** ``` # GitLab format - kind: folder selector: folders: - path: "src" repos: - "backend-service" # ❌ Simple name branch: "main" # ❌ Single branch for all repos # GitLab-v2 format - kind: folder selector: folders: - path: "src" repos: - name: "my-org/backend-service" # ✅ Full namespace with branch object branch: "main" - name: "my-org/frontend-app" # ✅ Different branches per repo branch: "develop" ``` #### Migration Pattern Comparison[​](#migration-pattern-comparison "Direct link to Migration Pattern Comparison") | V1 Pattern | V2 Pattern | What it matches | Migration Notes | | ------------------------------------- | ------------------------------------------------------------------ | ----------------------------------- | ----------------------------------------------- | | `**/infra/**/terraform/*.{tf,tfvars}` | `*.tf` + `*.tfvars` | Terraform files anywhere | Split into multiple file kinds | | `**/infra/**/terraform/*.{tf,tfvars}` | `infra/prod/terraform/main.tf` + `infra/staging/terraform/main.tf` | Terraform files in specific paths | Split into multiple file kinds with exact paths | | `**/package.json` | `package.json` | Files named "package.json" anywhere | Searches entire repository | | `**/Dockerfile` | `Dockerfile` | All Dockerfiles anywhere | Searches entire repository | | `**/docker-compose.yml` | `docker-compose.yml` | Docker Compose files anywhere | Searches entire repository | GitLab Advanced Search capabilities GitLab-v2 follows [GitLab's Advanced Search syntax](https://docs.gitlab.com/user/search/advanced_search/#use-advanced-search) which supports: * `*` for partial matches (e.g., `*package.json` or `*.yml`) * `filename:` prefix for filename searches * Other advanced search operators See GitLab's documentation for the complete list of supported search patterns and limitations. This change ensures accurate repository identification and prevents conflicts when multiple repositories share the same name across different GitLab groups. ### Step 6: Deploy and verify[​](#step-6-deploy-and-verify "Direct link to Step 6: Deploy and verify") 1. **Deploy the GitLab-v2 integration** with your updated configuration (follow [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#setup) on the GitLab v2 integration page for hosted, self-hosted, or CI installs) 2. **Trigger a manual resync** from Port's UI to initiate data resync 3. **Verify data consistency**: * Check that all expected projects are resynced * Confirm merge requests and issues are updating correctly * Test webhook functionality if enabled 4. **Monitor the logs** for any authentication or permission issues Parallel testing Consider running both GitLab and GitLab-v2 integrations temporarily to compare results before fully switching over. ### Step 7: Clean up GitLab integration[​](#step-7-clean-up-gitlab-integration "Direct link to Step 7: Clean up GitLab integration") Once GitLab-v2 is working correctly: 1. **Disable the GitLab integration** 2. **Revoke the old group access tokens** 3. **Remove GitLab configuration files** 4. **Update any documentation** referencing the old setup This cleanup ensures security by removing unused tokens and prevents confusion from having multiple configurations. ## Webhooks and self-managed differences[​](#webhooks-and-self-managed-differences "Direct link to Webhooks and self-managed differences") The GitLab and GitLab-v2 integrations expose different knobs for live events. Self-hosted installs should align `base_url` / networking with the guidance in [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#setup) so GitLab can reach the integration for webhook delivery: * **GitLab (v1)** exposed configuration like `appHost`, `useSystemHook`, and `tokenGroupHooksOverrideMapping` to control where and how webhooks were registered. * **GitLab-v2** registers **group webhooks** automatically when the integration is configured with a reachable `base_url` and a token that can create webhooks. On Port's hosted (SaaS) deployment, live events are enabled by default (`liveEvents.enabled: true` in the integration spec). For self-managed setups, ensure that: * The integration is reachable from your GitLab instance at a stable URL (the equivalent of v1's `appHost`). * The service account token has permission to create and manage group webhooks for the groups you want to sync. ## GitOps[​](#gitops "Direct link to GitOps") The v1 integration declared a `gitops` feature in its spec; the v2 integration spec lists the `exporter` feature only. If you rely on GitOps workflows (committing entity manifests to GitLab and having Port sync them), read the [GitLab v2 GitOps guide](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/gitops/.md) and the [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#gitops) section on the GitLab v2 integration page before fully decommissioning v1. ## New features in GitLab-v2[​](#new-features-in-gitlab-v2 "Direct link to New features in GitLab-v2") ### Pipeline jobs support[​](#pipeline-jobs-support "Direct link to Pipeline jobs support") GitLab-v2 introduces comprehensive pipeline job tracking, allowing you to monitor CI/CD job status, stages, and duration. This feature provides detailed insight into your build and deployment processes. ``` - kind: job selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"gitlabJob"' properties: status: .status stage: .stage duration: .duration ``` ### Enhanced group management[​](#enhanced-group-management "Direct link to Enhanced group management") Groups in GitLab-v2 provide cleaner member resync and better hierarchy handling. The separation of member and group concerns allows for more flexible data modeling and improved relationship management between users, groups, and projects. ## Troubleshoot common issues[​](#troubleshoot-common-issues "Direct link to Troubleshoot common issues") For integration health checks and common setup errors, also see [Troubleshooting](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md#troubleshooting) on the GitLab v2 integration page. | Issue | Cause | Solution | | --------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | **Authentication errors** | Service account lacks access to required groups | Verify service account has been added to all relevant groups with appropriate permissions | | **Missing repositories** | Incorrect namespace formatting in GitLab-v2 | Ensure repository paths use the full `namespace/project` format | | **Missing member data** | Member selectors not aligned with v2 (e.g. relying on removed `includeVerboseMemberObject`) | Remove unsupported selectors. Either keep `project-with-members` / `group-with-members`, or switch to the dedicated `member` kind with relations. | | **Member relation errors** | Members modeled as embedded properties when relations were expected | Update `group-with-members` (or `project-with-members`) to use relations to a `member` blueprint instead of embedded member properties | | **Missing closed/merged MRs after migration** | v2 default `states: ["opened"]` and `updatedAfter` window | Set `states` to include `merged` / `closed` and tune `updatedAfter` to your retention needs | | **Webhook failures** | Service account lacks webhook creation permissions | Check that service account has sufficient privileges to create and manage webhooks | | **File sync issues** | Incorrect search syntax | Update file paths to use GitLab Advanced Search syntax (e.g., `*package.json` instead of `**/package.json`) | | **Repository not found errors** | Outdated repository names in file/folder selectors | Update repo names to use full namespace format (`org/repo`) | If you encounter issues during migration: 1. Check the integration logs in Port's UI for specific error messages 2. Verify your service account permissions in GitLab to ensure proper access 3. Contact Port support with your specific error messages and configuration details This migration ensures your GitLab integration benefits from improved security through centralized token management, enhanced performance with optimized API calls, and new capabilities including pipeline job tracking while maintaining all your existing data resync patterns. --- # GitLab ![GitLab icon](/img/guides/icons/GitLab.svg)![GitLab icon](/img/guides/icons/GitLab.svg) Loading version... Port's GitLab integration allows you to model GitLab resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Group](https://docs.gitlab.com/ee/api/groups.html#list-groups)[Project](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects)[Merge Request](https://docs.gitlab.com/ee/api/merge_requests.html#list-project-merge-requests)[Pipeline](https://docs.gitlab.com/ee/api/pipelines.html#list-project-pipelines)[Job](https://docs.gitlab.com/ee/api/jobs.html#list-project-jobs)[Issue](https://docs.gitlab.com/ee/api/issues.html#list-project-issues)[File](https://docs.gitlab.com/ee/api/repository_files.html#get-file-from-repository)[Folder](https://docs.gitlab.com/ee/api/repositories.html#list-repository-tree)[Group with Members](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project)[Project with Members](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project) ## Core support [Live eventsSee supported resources](#configure-realtime-webhook-events) Deprecation notice This integration will be deprecated in the future and support for it will be discontinued soon. To integrate Port with GitLab, we recommend using the [GitLab V2 integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md). See the [migration guide](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/migration/.md) to learn how to migrate from v1 to v2 of the integration. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A GitLab account with admin privileges. * A GitLab group account with the `api` scope. * Your Port user role is set to `Admin`. ## Setup[​](#setup "Direct link to Setup") ### Create a GitLab group access token[​](#create-a-gitlab-group-access-token "Direct link to Create a GitLab group access token") A group access token can be used for the group it was generated at, as well as for all sub-groups underneath it. The GitLab integration is able to query multiple GitLab root groups. To do so, it will require multiple group access tokens, each at the correct root group. **GitLab group access tokens example (click to expand)** For example, let's assume the following GitLab account structure: ``` GitLab account . ├── microservices-group │ ├──microservice1-group │ └──microservice2-group ├── apis-group │ ├── rest-api-group │ └── graphql-api-group ``` In this example: * To map **only** the `microservices-group`, we require one group access token - one for the `microservices-group`. * To map the `microservices-group` **and** all of its subgroups, we require only one group access token - one for the `microservices-group`. * To map the `microservices-group`, **the** `apis-group` **and** all of their subgroups, we require only two group access tokens - one for the `microservices-group` and one for the `apis-group`. * To map the `microservice1-group`, we have 2 options: * Create a group access token for the `microservices-group` and use the [token mapping](#token-mapping) to select just the `microservice1-group`. * Create a group access token for the `microservice1-group` directly. See the [Token Mapping](#token-mapping) section for more information. The following steps will guide you how to create a GitLab group access token. 1. Sign in to GitLab and go to your desired group's settings page: 2. In the "Access Tokens" section, you need to provide the token details, including the name and an optional expiration date. Additionally, select the api scope, and then proceed to click on the "Create access token" button. ![GitLab access token form with token name, expiration date, Owner role, and API scope selected](/img/integrations/gitlab/GitLabGroupAccessTokens.png) Lower permission levels While `Owner` permissions are recommended, you can use the `filterOwnedProjects: false` flag in the mapping to set lower permission levels (for more information, [refer to the Advanced configurations tab](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/advanced/.md?parameter=filterOwnedProjects#using-advanced-configurations)). *Please note that using lower permissions may lead to unexpected behavior.* 3. Click `Create group access token`. 4. Copy the generated token and use it when deploying the integration in the following steps. ### Token Mapping[​](#token-mapping "Direct link to Token Mapping") The GitLab integration supports fetching data related to specific paths in your GitLab groups. The integration is also able to fetch data from different GitLab parent groups by providing additional group tokens. In order to do so, you need to map the desired paths to the relevant access tokens. The `tokenMapping` parameter supports specifying the paths that the integration will search for files and information in, using [globPatterns](https://www.malikbrowne.com/blog/a-beginners-guide-glob-patterns). Mapping format: ``` {"MY_FIRST_GITLAB_PROJECT_GROUP_TOKEN": ["**/MyFirstGitLabProject/**","**/MySecondGitLabProject/*"]} ``` Example: ``` {"glpat-QXbeg-Ev9xtu5_5FsaAQ": ["**/DevopsTeam/*Service", "**/RnDTeam/*Service"]} ``` Helm installation parameter When using the `tokenMapping` parameter in the integration's Helm installation, make sure to escape the necessary characters, for example: ``` --set integration.secrets.tokenMapping="\{\"glpat-oh1YXc54pR4eofx6hYy5\": [\"**\"]\}" ``` Multiple GitLab group access tokens example: ``` {"glpat-QXbeg-Ev9xtu5_5FsaAQ": ["**/DevopsTeam/*Service", "**/RnDTeam/*Service"],"glpat-xF7Ae-vXu5ts5_QbEgAQ9": ["**/MarketingTeam/*Service"]} ``` ### Configure Realtime webhook events[​](#configure-realtime-webhook-events "Direct link to Configure Realtime webhook events") Real-time updates The `appHost` parameter is used specifically to enable the real-time functionality of the integration. If it 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. In order for the GitLab integration to update the data in Port on every change in the GitLab repository, you need to specify the `appHost` parameter. The `appHost` parameter should be set to the `url` of your GitLab integration instance. In addition, your GitLab instance (whether it is GitLab SaaS or a self-hosted version of GitLab) needs to have the option to send webhook requests to the GitLab integration instance, so please configure your network accordingly. **The default webhook events behavior** The GitLab integration supports listening to GitLab webhooks and updating the relevant entities in Port accordingly. Supported webhooks are [Group webhooks](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#group-webhooks) and [System hooks](https://docs.gitlab.com/ee/administration/system_hooks.html). As part of the installation process, the integration will create a webhook in your GitLab instance, and will use it to listen to the relevant events. ***There are a few points to consider before deciding on which webhook to choose*** : * If you choose system hooks, the integration will create a single webhook for the entire GitLab instance. If you choose group webhooks, the integration will create a webhook for each root group in your GitLab instance, unless you provide the `tokenGroupHooksOverrideMapping` parameter- and then it will create a webhook for each specified group in this parameter. * System hooks support far fewer event types than group webhooks. If you choose system hooks, the integration will not be able to update entities in Port for events such as `issues_events`, `pipeline_events`, etc. **Supported event types per webhook type (click to expand)** **Group Webhooks** support the following event types: * `push_events` * `issues_events` * `jobs_events` * `merge_requests_events` * `pipeline_events` * `releases_events` * `tag_push_events` * `subgroup_events` * `confidential_issues_events` * `user_remove_from_group` * `user_update_for_group` * `user_add_to_group` **System Hooks** support only: * `push_events` * `merge_requests_events` * `repository_update_events` * Creating a system hook requires admin privileges in GitLab. Due to this, the integration supports that the system hook will be created manually, and the integration will use it to listen to the relevant events. **Specific group webhooks** By default, if `appHost` is provided, the integration will create group webhooks for each root group in your GitLab instance. If you need to create webhooks only for specific groups, you should configure the [`tokenGroupHooksOverrideMapping`](#listen-to-specified-groups) parameter. **System webhooks** To create a system hook in GitLab, you must set `useSystemHook: true` and choose one of the following setup methods: 1. **Automatic Setup** - Provide a token with admin privileges using the `tokenMapping` parameter. 2. **Manual Setup** - Create the system hook yourself in GitLab: * Follow GitLab's [system hook setup guide](https://docs.gitlab.com/ee/administration/system_hooks.html#create-a-system-hook). * Set the URL to `{appHost}/integration/system/hook` (e.g., `https://my-gitlab-integration.com/integration/system/hook`). * Enable the supported triggers: `push` and `merge_request`. Repository setup and initial commit Due to GitLab webhook limitations, new repositories require special handling: * A repository will only appear in Port after its first commit (GitLab webhooks don't support `project_create` events). * Empty repositories (no commits) will only appear after the next scheduled resync. For more details, see GitLab's [webhook events documentation](https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html). ![GitLab system hook configuration with push and merge request events selected](/assets/images/GitLabSystemHook-2c2b2c5fc276fb2be91c9004c3fc18e7.png) ### Listen to specified groups[​](#listen-to-specified-groups "Direct link to Listen to specified groups") The integration can support listening to webhooks on specified groups, by configuring the `tokenGroupHooksOverrideMapping` parameter. This parameter is not required, and when you don't use it, the integration will listen to all of the root groups (if not using `useSystemHooks=true`). Mapping format: ``` {"MY_FIRST_GROUPS_TOKEN": {"groups:"{"MY_FIRST_GROUP_FULL_PATH": {"events": [CHOSEN_EVENT_TYPES]}, "MY_OTHER_GROUP_FULL_PATH": {"events": [CHOSEN_EVENT_TYPES]}}}} ``` Example: ``` {"glpat-QXbeg-Ev9xtu5_5FsaAQ": {"groups": {"path/to/my-first-group": {"events": ["push_events", "merge_requests_events]}, "path/to/my-other-group": {"events": ["pipelines_events"]}}}} ``` You can configure multiple tokens, and multiple groups per token (the token should have admin access to those groups), but there are some rules: * All of the tokens mentioned here must be contained in `tokenMapping`. * A "groups" key is required for each token. * All of the groups in all of the tokens must be non-hierarchical to each other, and not identical (duplicated). * The group path is the full path in gitlab. If a group path is incorrect, the webhook will not be created. * The events for each group must match the supported event types mentioned below. if you would like to have all the events provided in the webhook, you can use: `{"events" = []}`, but not eliminate this key completely, because it is required. ### Deploy the GitLab integration[​](#deploy-the-gitlab-integration "Direct link to Deploy the GitLab integration") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: group-with-members selector: query: 'true' includeBotMembers: 'true' includeInheritedMembers: 'true' port: itemsToParse: .__members entity: mappings: identifier: .item.username title: .item.name blueprint: '"gitlabMember"' properties: link: .item.web_url state: .item.state email: .item.email locked: .item.locked - kind: group-with-members selector: query: 'true' includeBotMembers: 'true' port: entity: mappings: identifier: .full_path title: .name blueprint: '"gitlabGroup"' properties: url: .web_url visibility: .visibility description: .description relations: gitlabMembers: .__members | map(.username) - kind: project selector: query: 'true' port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"gitlabRepository"' properties: url: .web_url readme: file://README.md description: .description language: .__languages | to_entries | max_by(.value) | .key - kind: merge-request selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabMergeRequest"' properties: status: .state createdAt: .created_at updatedAt: .updated_at mergedAt: .merged_at link: .web_url leadTimeHours: (.created_at as $createdAt | .merged_at as $mergedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($mergedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $mergedTimestamp | if $mergedTimestamp == null then null else (((($mergedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) end) relations: repository: .references.full | gsub("!.+"; "") assignees_git_lab: .assignees | map(.username) reviewers_git_lab: .reviewers | map(.username) creator_git_lab: .author.username reviewers: combinator: '"and"' rules: - operator: '"in"' property: '"git_lab_username"' value: .reviewers | map(.username) assignees: combinator: '"and"' rules: - operator: '"in"' property: '"git_lab_username"' value: .assignees | map(.username) service: combinator: '"and"' rules: - operator: '"="' property: '"git_lab_repository_identifier"' value: .references.full | gsub("!.+"; "") creator: combinator: '"and"' rules: - operator: '"="' property: '"git_lab_username"' value: .author.username ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view blueprints and mapping configurations for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/examples/.md). ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Capabilities[​](#capabilities "Direct link to Capabilities") For detailed information about data ingestion capabilities, see the [capabilities page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/capabilities/.md). ## Examples[​](#examples "Direct link to Examples") Refer to the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/examples/.md) page for practical configurations and their corresponding blueprint definitions. ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=GitLab). ## GitOps[​](#gitops "Direct link to GitOps") Port's GitLab integration also provides GitOps capabilities, refer to the [GitOps](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/gitops/.md) page to learn more. ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/advanced/.md) page for advanced use cases and examples. --- # Advanced The GitLab integration supports additional flags to provide additional configuration, making it easier to configure its behavior to your liking. To use the advanced configuration and additional flags, add them as a root key to your [integration configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#the-integration-configuration), for example to add the `createMissingRelatedEntities` flag: ``` createMissingRelatedEntities: false resources: - kind: merge-request selector: query: "true" port: entity: mappings: ... mappings configuration ``` ## Using advanced configurations[​](#using-advanced-configurations "Direct link to Using advanced configurations") The following advanced configuration parameters are available and can be added to the [integration configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#the-integration-configuration): * branch * Spec path * Delete dependent entities * Enable merge entity * Create missing related entities * Filter owned projects * Filter projects The `branch` parameter determines the branch the integration will sync on.
By default this parameter is not set and the integration will sync all projects using their default branch. The `specPath` parameter specifies a list of [globPatterns](https://www.malikbrowne.com/blog/a-beginners-guide-glob-patterns)\[] that Port's GitLab app will search for `port.yml` files in. * **Default value**: `**/port.yml` * **Use case**: * If you want the app to scan a different file than `port.yml` (for example, change configure the app to scan files named `my-port-config.yml` using the pattern `**/my-port-config.yml`). * If you want the app to ignore `port.yml` files in certain paths. The `deleteDependentEntities` parameter is used to enable deletion of dependent Port entities. This is useful when you have two blueprints with a required relation, and the target entity in the relation should be deleted. In this scenario, the delete operation will fail if this flag is set to `false` if the flag is set to `true`, the source entity will be deleted as well. * **Default value**: `false` (disabled) * **Use case**: Deletion of dependent Port entities. Must be enabled, if you want to delete a target entity (and its source entities) in a required relation. The `enableMergeEntity` parameter specifies whether to use the [create/update](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-update#usage) or [create/override](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md?operation=create-override#usage) strategy when creating entities listed in a `port.yml` file. * **Default value**: `false` (use create/override) * **Use case**: use `false` if you want GitLab to be the source-of-truth for catalog entities. Use `true` if you want to use GitLab as the source for some properties of entities in the catalog, and use other sources to for properties which are subject to change automatically. The `createMissingRelatedEntities` parameter is used to enable the creation of missing related Port entities automatically in cases where the target related entity does not exist in the software catalog yet. * **Default value**: `true` to allow the GitLab app to create barebones related entities, in case those related entities do not exist in the software catalog. * **Use case**: use `false` if you do not want this default behavior (do not create missing related entities). By default the gitlab integration will sync only projects that are owned by the token provided in the integration configuration. The `filterOwnedProjects` parameter is used to filter out projects that are not owned by the token. This parameter is useful when you want to sync projects that the token has access to but is not set as the owner of it. The equivalent of this parameter in the gitlab API is the `owned` parameter for the [list projects](https://docs.gitlab.com/ee/api/projects.html#list-user-projects) endpoint. By default this parameter is not set and the integration will sync all projects that the token has access to. The `projectVisibilityFilter` parameter is used to filter out projects based on their visibility level in GitLab (public, internal, private). The equivalent of this parameter in the gitlab API is the `visibility` parameter for the [list projects](https://docs.gitlab.com/ee/api/projects.html#list-user-projects) endpoint. --- # Capabilities This page describes the data ingestion capabilities of Port's GitLab integration. ## Ingest files from your repositories[​](#ingest-files-from-your-repositories "Direct link to Ingest files from your repositories") Port allows you to fetch `JSON` and `YAML` files from your repositories, and create entities from them in your software catalog. This is done using the `file` kind in your GitLab mapping configuration. For example, say you want to manage your `package.json` files in Port. One option is to create a `manifest` blueprint, with each of its entities representing a `package.json` file. Search Type The `file` kind follows [GitLab's Advanced Search type](https://docs.gitlab.com/ee/user/search/advanced_search.html#:~:text=Advanced%20search%20is%20based%20on,Projects/), adhering to its syntax, limitations, and capabilities. The following configuration fetches all `package.json` files from "MyRepo" and "MyOtherRepo", and creates an entity for each of them, based on the `manifest` blueprint: ``` 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. # Remove the `repos` key if you want to fetch files from all repository repos: # The repository NAME should be used here, not the URL slug, e.g. "Repo Name" not "repo-name" - "MyRepoName" - "MyOtherRepoName" port: entity: mappings: identifier: .file.file_path title: .file.file_name blueprint: '"manifest"' properties: project_name: .file.content.name project_version: .file.content.version license: .file.content.license ``` Test your mapping After adding the `file` kind to your mapping configuration, click on the `Resync` button. When you open the mapping configuration again, you will see real examples of files fetched from your GitLab organization. This will help you see what data is available to use in your `jq` expressions. Click on the `Test mapping` button to test your mapping against the example data. In any case, the structure of the available data looks like this: **Available data example (click to expand)** ``` { "file": { "file_name": "package.json", "file_path": "package.json", "size": 780, "encoding": "base64", "content_sha256": "d4dffc856dcacdaecb283ba9e47288beb6036d7c4ffff7c65f29057d890ecee9", "ref": "main", "blob_id": "7f8c2fea237a5cf0e1bcc17135c7c8b9e96edd49", "commit_id": "3ac75a99f6faa8ce4570368e6db1038c15f17cfc", "last_commit_id": "3ac75a99f6faa8ce4570368e6db1038c15f17cfc", "execute_filemode": false, "content": { "name": "my-awesome-project", "version": "1.0.0", "description": "A sample Node.js project", "main": "index.js", "scripts": { "start": "node index.js", "test": "echo \"Error: no test specified\" && exit 1", "build": "echo \"Building the project...\"", "lint": "eslint ." }, "repository": { "type": "git", "url": "git+https://github.com/username/my-awesome-project.git" }, "keywords": [ "sample", "nodejs", "project" ], "author": "Your Name", "license": "ISC", "bugs": { "url": "https://github.com/username/my-awesome-project/issues" }, "homepage": "https://github.com/username/my-awesome-project#readme", "dependencies": { "@docsly/react": "^1.9.1", "@docusaurus/core": "^3.4.0", "@docusaurus/plugin-client-redirects": "^3.4.0", "@docusaurus/plugin-google-tag-manager": "^3.4.0", "@docusaurus/plugin-ideal-image": "^3.4.0", "@docusaurus/preset-classic": "^3.4.0", "@docusaurus/theme-live-codeblock": "^3.4.0", "@easyops-cn/docusaurus-search-local": "^0.44.2", "@mdx-js/react": "^3.0.1", "@port-labs/docusaurus-plugin-openapi-docs": "^0.0.5", "@port-labs/docusaurus-theme-openapi-docs": "^0.0.5", "@stackql/docusaurus-plugin-hubspot": "^1.0.1", "clsx": "^2.1.1", "docusaurus-plugin-hotjar": "^0.0.2", "docusaurus-plugin-image-zoom": "^2.0.0", "prettier": "^3.3.2", "prism-react-renderer": "^2.3.1", "react": "^18.3.1", "react-dom": "^18.3.1", "react-tooltip": "^5.27.1" }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.4.0", "@docusaurus/tsconfig": "^3.4.0", "@docusaurus/types": "^3.4.0", "husky": "^9.0.11", "pretty-quick": "^4.0.0", "typescript": "~5.5.3" } } }, "repo": { "id": 60625101, "description": null, "name": "pages-test-project", "name_with_namespace": "port-labs / pages-test-project", "path": "pages-test-project", "path_with_namespace": "port-org/pages-test-project", "created_at": "2024-08-06T10:31:47.509Z", "default_branch": "main", "tag_list": [], "topics": [], "ssh_url_to_repo": "git@gitlab.com:port-org/pages-test-project.git", "http_url_to_repo": "https://gitlab.com/port-org/pages-test-project.git", "web_url": "https://gitlab.com/port-org/pages-test-project", "readme_url": "https://gitlab.com/port-org/pages-test-project/-/blob/main/README.md", "forks_count": 0, "avatar_url": null, "star_count": 0, "last_activity_at": "2024-08-14T15:40:56.606Z", "namespace": { "id": 66136652, "name": "port-labs", "path": "port-org", "kind": "group", "full_path": "port-org", "parent_id": null, "avatar_url": null, "web_url": "https://gitlab.com/groups/port-org" }, "container_registry_image_prefix": "registry.gitlab.com/port-org/pages-test-project", "_links": { "self": "https://gitlab.com/api/v4/projects/60625101", "issues": "https://gitlab.com/api/v4/projects/60625101/issues", "merge_requests": "https://gitlab.com/api/v4/projects/60625101/merge_requests", "repo_branches": "https://gitlab.com/api/v4/projects/60625101/repository/branches", "labels": "https://gitlab.com/api/v4/projects/60625101/labels", "events": "https://gitlab.com/api/v4/projects/60625101/events", "members": "https://gitlab.com/api/v4/projects/60625101/members", "cluster_agents": "https://gitlab.com/api/v4/projects/60625101/cluster_agents" }, "packages_enabled": true, "empty_repo": false, "archived": false, "visibility": "private", "resolve_outdated_diff_discussions": false, "container_expiration_policy": { "cadence": "1d", "enabled": false, "keep_n": 10, "older_than": "90d", "name_regex": ".*", "name_regex_keep": null, "next_run_at": "2024-08-07T10:31:47.544Z" }, "repository_object_format": "sha1", "issues_enabled": true, "merge_requests_enabled": true, "wiki_enabled": true, "jobs_enabled": true, "snippets_enabled": true, "container_registry_enabled": true, "service_desk_enabled": true, "service_desk_address": "contact-project-pages-test-project-60625101-issue-@incoming.gitlab.com", "can_create_merge_request_in": true, "issues_access_level": "enabled", "repository_access_level": "enabled", "merge_requests_access_level": "enabled", "forking_access_level": "enabled", "wiki_access_level": "enabled", "builds_access_level": "enabled", "snippets_access_level": "enabled", "pages_access_level": "private", "analytics_access_level": "enabled", "container_registry_access_level": "enabled", "security_and_compliance_access_level": "private", "releases_access_level": "enabled", "environments_access_level": "enabled", "feature_flags_access_level": "enabled", "infrastructure_access_level": "enabled", "monitor_access_level": "enabled", "model_experiments_access_level": "enabled", "model_registry_access_level": "enabled", "emails_disabled": false, "emails_enabled": true, "shared_runners_enabled": true, "lfs_enabled": true, "creator_id": 6152768, "import_url": null, "import_type": null, "import_status": "none", "open_issues_count": 0, "description_html": "", "updated_at": "2024-08-14T15:40:56.606Z", "ci_default_git_depth": 20, "ci_forward_deployment_enabled": true, "ci_forward_deployment_rollback_allowed": true, "ci_job_token_scope_enabled": false, "ci_separated_caches": true, "ci_allow_fork_pipelines_to_run_in_parent_project": true, "ci_id_token_sub_claim_components": [ "project_path", "ref_type", "ref" ], "build_git_strategy": "fetch", "keep_latest_artifact": true, "restrict_user_defined_variables": false, "ci_pipeline_variables_minimum_override_role": "maintainer", "runners_token": null, "runner_token_expiration_interval": null, "group_runners_enabled": true, "auto_cancel_pending_pipelines": "enabled", "build_timeout": 3600, "auto_devops_enabled": false, "auto_devops_deploy_strategy": "continuous", "ci_push_repository_for_job_token_allowed": false, "ci_config_path": "", "public_jobs": true, "shared_with_groups": [], "only_allow_merge_if_pipeline_succeeds": false, "allow_merge_on_skipped_pipeline": null, "request_access_enabled": true, "only_allow_merge_if_all_discussions_are_resolved": false, "remove_source_branch_after_merge": true, "printing_merge_request_link_enabled": true, "merge_method": "merge", "squash_option": "default_off", "enforce_auth_checks_on_uploads": true, "suggestion_commit_message": null, "merge_commit_template": null, "squash_commit_template": null, "issue_branch_template": null, "warn_about_potentially_unwanted_characters": true, "autoclose_referenced_issues": true, "approvals_before_merge": 0, "mirror": false, "external_authorization_classification_label": "", "marked_for_deletion_at": null, "marked_for_deletion_on": null, "requirements_enabled": true, "requirements_access_level": "enabled", "security_and_compliance_enabled": true, "pre_receive_secret_detection_enabled": false, "compliance_frameworks": [], "issues_template": null, "merge_requests_template": null, "ci_restrict_pipeline_cancellation_role": "developer", "merge_pipelines_enabled": false, "merge_trains_enabled": false, "merge_trains_skip_train_allowed": false, "only_allow_merge_if_all_status_checks_passed": false, "allow_pipeline_trigger_approve_deployment": false, "prevent_merge_without_jira_issue": false, "permissions": { "project_access": null, "group_access": { "access_level": 50, "notification_level": 3 } } } } ``` ### Create multiple entities from a single file[​](#create-multiple-entities-from-a-single-file "Direct link to Create multiple entities from a single file") In some cases, we would like to parse a single JSON/YAML file and create multiple entities from it. For this purpose, we can use the `itemsToParse` key in our mapping configuration. For example, say you want to track/manage a project's dependencies in Port. One option is to create a `package` blueprint, with each of its entities representing a dependency from a `package.json` file. The following configuration fetches a `package.json` file from a specific repository, and creates an entity for each of the dependencies in the file, based on the `package` blueprint: ``` resources: - kind: file selector: query: 'true' files: path: '**/package.json' # Note that in this case we are fetching from a specific repository repos: - MyRepoName port: itemsToParse: .file.content.dependencies | to_entries entity: mappings: # Since identifier cannot contain special characters, we are using jq to remove them identifier: >- .item.key + "_" + if (.item.value | startswith("^")) then .item.value[1:] else .item.value end title: .item.key + "@" + .item.value blueprint: '"package"' properties: package: .item.key version: .item.value relations: {} ``` The `itemsToParse` key is used to specify the path to the array of items you want to parse from the file. In this case, we are parsing the `dependencies` array from the `package.json` file. Once the array is parsed, we can use the `item` key to refer to each item in the array. ### Multi-document YAML files[​](#multi-document-yaml-files "Direct link to Multi-document YAML files") For multi-document YAML files (a single file containing multiple YAML documents separated by `---`), `.file.content` will not resolve to an object, but to an array of objects. You can use one of these methods to ingest multi-document YAML files: 1. Use the `itemsToParse` key to create multiple entities from such a file (see example above). 2. Map the result to an `array` property. Mixed YAML types If you have both single-document and multi-document YAML files in your repositories, you can use the `itemsToParse` key like this to handle both cases: ``` itemsToParse: .file.content | if type== "object" then [.] else . end ``` ### Limitations[​](#limitations "Direct link to Limitations") * Currently only files up to 1MB in size are supported. * Only JSON and YAML formats are automatically parsed. Other file formats can be ingested as raw files, however, some special characters in the file (such as `\n`) may be processed and not preserved. * GLOB patterns are supported for file pattern matching, but wildcards at the end (e.g., `**/*`) are not allowed, in order to prevent matching all files indiscriminately. * Currently only the default branch of the repository is supported. For a list of known limitations with GitLab's Advanced Search, see GitLab's [Advanced Search documentation](https://docs.gitlab.com/ee/user/search/advanced_search.html#known-issues). --- # Examples This page provides practical examples for mapping various GitLab resources to Port. ## Projects, README.md and merge requests[​](#projects-readmemd-and-merge-requests "Direct link to Projects, README.md and merge requests") The following example demonstrates how to ingest your GitLab projects, their README.md file contents and merge requests to Port.
You can use the following Port blueprint definitions and integration configuration: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" }, "labels": { "items": { "type": "object" }, "type": "array", "title": "Labels" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Merge request blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabMergeRequest", "title": "Merge Request", "icon": "GitVersion", "schema": { "properties": { "creator": { "title": "Creator", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": [ "opened", "closed", "merged", "locked" ], "enumColors": { "opened": "yellow", "closed": "red", "merged": "green", "locked": "blue" } }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "mergedAt": { "title": "Merged At", "type": "string", "format": "date-time" }, "link": { "title": "Link", "format": "url", "type": "string" }, "leadTimeHours": { "title": "Lead Time in hours", "type": "number" }, "reviewers": { "title": "Reviewers", "type": "array", "items": { "type": "string" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "gitlabRepository", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: "true" includeLabels: "true" port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"gitlabRepository"' properties: url: .web_url readme: file://README.md description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch labels: .__labels - kind: merge-request selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabMergeRequest"' properties: creator: .author.name status: .state createdAt: .created_at updatedAt: .updated_at mergedAt: .merged_at link: .web_url leadTimeHours: >- (.created_at as $createdAt | .merged_at as $mergedAt | ($createdAt | sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) as $createdTimestamp | ($mergedAt | if . == null then null else sub("\\..*Z$"; "Z") | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime end) as $mergedTimestamp | if $mergedTimestamp == null then null else (((($mergedTimestamp - $createdTimestamp) / 3600) * 100 | floor) / 100) end) reviewers: .reviewers | map(.name) relations: repository: .references.full | gsub("!.+"; "") ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * Click [Here](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects) for the GitLab project object structure. * Click [Here](https://docs.gitlab.com/ee/api/merge_requests.html#list-project-merge-requests) for the GitLab merge request object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your projects alongside their `README.md` file contents and merge requests. ## Files and file contents[​](#files-and-file-contents "Direct link to Files and file contents") The following example shows how to ingest dependencies from a `package.json` file in your GitLab repository into Port.
You can use the following Port blueprint definitions and integration configuration: **Package blueprint (click to expand)** Create in Port ``` { "identifier": "package", "title": "Package", "icon": "Package", "schema": { "properties": { "package": { "icon": "DefaultProperty", "type": "string", "title": "Package" }, "version": { "icon": "DefaultProperty", "type": "string", "title": "Version" } }, "required": [ "package", "version" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: file selector: query: 'true' files: path: '**/package.json' repos: # The repository NAME should be used here, not the URL slug, e.g. "Repo Name" not "repo-name" - MyRepoName port: itemsToParse: .file.content.dependencies | to_entries entity: mappings: identifier: >- .item.key + "_" + if (.item.value | startswith("^")) then .item.value[1:] else .item.value end title: .item.key + "@" + .item.value blueprint: '"package"' properties: package: .item.key version: .item.value relations: {} ``` The example will parse the `package.json` file in your repository and extract the dependencies into Port entities.
For more information about ingesting files and file contents, click [here](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#ingest-files-from-your-repositories) ## Groups, subgroups and projects[​](#groups-subgroups-and-projects "Direct link to Groups, subgroups and projects") The following example demonstrates how to ingest your GitLab groups, subgroups and projects to Port.
You can use the following Port blueprint definitions and integration configuration: **Group blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabGroup", "title": "Group", "icon": "GitLab", "schema": { "properties": { "visibility": { "icon": "Lock", "title": "Visibility", "type": "string", "enum": [ "public", "internal", "private" ], "enumColors": { "public": "red", "internal": "yellow", "private": "green" } }, "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link" }, "description": { "title": "Description", "type": "string", "icon": "BlankPage" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "group": { "title": "Group", "target": "gitlabGroup", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: group selector: query: "true" port: entity: mappings: identifier: .full_path title: .name blueprint: '"gitlabGroup"' properties: url: .web_url visibility: .visibility description: .description - kind: project selector: query: "true" port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"gitlabRepository"' properties: url: .web_url readme: file://README.md description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch relations: group: >- .path_with_namespace | gsub(" "; "") | split("/") | .[:-1] | join("/") ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * Click [Here](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects) for the GitLab project object structure. * Click [Here](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-subgroups) for the GitLab subgroup object structure. ## Projects, pipelines and jobs[​](#projects-pipelines-and-jobs "Direct link to Projects, pipelines and jobs") The following example demonstrates how to ingest your GitLab projects, their pipelines and jobs runs to Port.
You can use the following Port blueprint definitions and integration configuration: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" }, "labels": { "items": { "type": "object" }, "type": "array", "title": "Labels" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Pipeline blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabPipeline", "title": "Pipeline", "icon": "GitLab", "schema": { "properties": { "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "creator": { "title": "Creator", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": [ "created", "waiting_for_resource", "preparing", "pending", "running", "success", "failed", "canceled", "skipped", "manual", "scheduled" ], "enumColors": { "created": "yellow", "waiting_for_resource": "yellow", "preparing": "yellow", "pending": "yellow", "running": "yellow", "success": "green", "failed": "red", "canceled": "red", "skipped": "red", "manual": "blue", "scheduled": "blue" } }, "stages": { "title": "Stages", "type": "array", "items": { "type": "string" } }, "link": { "title": "Link", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "gitlabRepository", "required": true, "many": false } } } ``` **Job blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabJob", "title": "Job", "icon": "GitLab", "schema": { "properties": { "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "startedAt": { "title": "Started At", "type": "string", "format": "date-time" }, "finishedAt": { "title": "Finished At", "type": "string", "format": "date-time" }, "creator": { "title": "Creator", "type": "string" }, "stage": { "title": "Stage", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": [ "failed", "warning", "pending", "running", "manual", "scheduled", "canceled", "success", "skipped", "created" ], "enumColors": { "failed": "red", "warning": "red", "pending": "yellow", "running": "yellow", "manual": "blue", "scheduled": "blue", "canceled": "red", "success": "green", "skipped": "red", "created": "yellow" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "pipeline": { "target": "gitlabPipeline", "required": false, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: "true" port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"gitlabRepository"' properties: url: .web_url description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch - kind: pipeline selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .title // (.id | tostring) blueprint: '"gitlabPipeline"' properties: creator: .user.name status: .status createdAt: .created_at updatedAt: .updated_at description: .description link: .web_url relations: repository: .__project.path_with_namespace | gsub(" "; "") - kind: job selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"gitlabJob"' properties: creator: .user.name startedAt: .started_at updatedAt: .updated_at finishedAt: .finished_at stage: .stage status: .status link: .web_url relations: pipeline: .pipeline.id | tostring ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * Click [Here](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects) for the GitLab project object structure. * Click [Here](https://docs.gitlab.com/ee/api/pipelines.html#list-project-pipelines) for the GitLab pipeline object structure. * Click [Here](https://docs.gitlab.com/ee/api/jobs.html#list-project-jobs) for the GitLab job object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your projects alongside their pipelines and jobs. ## Projects and monorepos[​](#projects-and-monorepos "Direct link to Projects and monorepos") The following example demonstrates how to ingest your GitLab projects and their monorepo folders to Port.
You can use the following Port blueprint definitions and integration configuration: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" }, "labels": { "items": { "type": "object" }, "type": "array", "title": "Labels" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: "apps/" # Relative path to the folders within the repositories. repos: # List of repositories to include folders from. - backend-service - frontend-service port: entity: mappings: identifier: .folder.name title: .folder.name blueprint: '"gitlabProject"' properties: url: >- .repo.web_url + "/tree/" + .repo.default_branch + "/" + .folder.path description: .repo.description namespace: .repo.namespace.name full_path: .repo.path_with_namespace + "/" + .folder.path language: .repo.__languages | to_entries | max_by(.value) | .key readme: file://README.md ``` To retrieve the root folders of your monorepo, you can use this following syntax in your `port-app-config.yml`: ``` - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: "/" # Relative path to the folders within the repositories repos: # List of repositories to include folders from. - backend-service - frontend-service ``` You can also specify a different path for each monorepo repository, for example: ``` - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: "apps/" repos: - gaming-apps - path: "microservices/" repos: - backend-services ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * Click [Here](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects) for the GitLab project object structure. * Click [Here](https://docs.gitlab.com/ee/api/repositories.html#list-repository-tree) for the GitLab repository tree object structure. ## Projects and folders[​](#projects-and-folders "Direct link to Projects and folders") The following example demonstrates how to ingest your GitLab projects and their folders to Port.
You can use the following Port blueprint definitions and integration configuration: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" }, "labels": { "items": { "type": "object" }, "type": "array", "title": "Labels" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Folder blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabFolder", "title": "Folder", "icon": "Gitlab", "schema": { "properties": { "readme": { "title": "README", "type": "string", "format": "markdown" }, "url": { "title": "Folder URL", "type": "string", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "gitlabRepository", "required": false, "many": false } } } ``` **Port port-app-config.yml (click to expand)** ``` resources: - kind: repository selector: query: "true" port: entity: mappings: identifier: .name title: .name blueprint: '"gitlabRepository"' properties: readme: file://README.md url: .web_url defaultBranch: .default_branch - kind: folder selector: query: "true" folders: - path: "/" # Using "/" will ingest the folders from the root of each of repository as entities repos: # Add any repositories you want to map folders from to the following list - backend-service - frontend-service port: entity: mappings: identifier: .folder.name title: .folder.name blueprint: '"gitlabFolder"' properties: url: >- .repo.web_url + "/tree/" + .repo.default_branch + "/" + .folder.path readme: file://README.md relations: repository: .repo.name ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * Click [Here](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects) for the GitLab project object structure. * Click [Here](https://docs.gitlab.com/ee/api/repositories.html#list-repository-tree) for the GitLab repository tree object structure. ## Projects and issues[​](#projects-and-issues "Direct link to Projects and issues") The following example demonstrates how to ingest your GitLab projects and their issues to Port.
You can use the following Port blueprint definitions and integration configuration: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" }, "labels": { "items": { "type": "object" }, "type": "array", "title": "Labels" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Issue blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabIssue", "title": "Issue", "icon": "GitLab", "schema": { "properties": { "link": { "title": "Link", "type": "string", "format": "url" }, "description": { "title": "Description", "type": "string", "format": "markdown" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "closedAt": { "title": "Closed At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "creator": { "title": "Creator", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": ["opened", "closed"], "enumColors": { "opened": "green", "closed": "purple" } }, "labels": { "title": "Labels", "type": "array", "items": { "type": "string" } } } }, "calculationProperties": {}, "relations": { "repository": { "title": "Repository", "target": "gitlabRepository", "required": true, "many": false } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project selector: query: "true" port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"gitlabRepository"' properties: url: .web_url description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch - kind: issue selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"gitlabIssue"' properties: creator: .author.name status: .state createdAt: .created_at closedAt: .closed_at updatedAt: .updated_at description: .description link: .web_url labels: "[.labels[]]" relations: repository: .references.full | gsub("#.+"; "") ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * Click [Here](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects) for the GitLab project object structure. * Click [Here](https://docs.gitlab.com/ee/api/issues.html#list-project-issues) for the GitLab issue object structure. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your projects alongside their issues. ## Group or project and members[​](#group-or-project-and-members "Direct link to Group or project and members") The following example demonstrates how to ingest your GitLab members to Port.
You can use the following Port blueprint definitions and integration configuration: ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * When using **GitLab Self Hosted**, an admin token is required, rather than a group access token, to retrieve the `primary email addresses` of members. * When using **GitLab Enterprise**, accounts can retrieve the `primary email addresses` of members within their groups, provided the members are part of user accounts administered by an organization with [verified domains for groups](https://docs.gitlab.com/ee/user/enterprise_user/#verified-domains-for-groups). For more information, see [limitations](https://docs.gitlab.com/ee/api/members.html#limitations). GitLab free plan limitation Primary email addresses are not available for GitLab "Free plan" users. **Member blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabMember", "title": "Member", "icon": "GitLab", "schema": { "properties": { "email": { "type": "string", "title": "Email", "description": "GitLab primary email address.", "icon": "User", "format": "user" }, "link": { "icon": "Link", "type": "string", "title": "Link", "format": "url", "description": "URL link to the GitLab member." }, "state": { "title": "State", "type": "string", "icon": "GitLab", "description": "The current state of the GitLab member (e.g., active)." }, "locked": { "type": "string", "title": "Locked", "icon": "GitLab", "description": "Indicates if the GitLab member is locked." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration mapping (click to expand)** ``` resources: - kind: group-with-members # replace with `project-with-members` to retrieve members from projects selector: query: 'true' includeVerboseMemberObject: 'true' port: itemsToParse: .__members entity: mappings: identifier: .item.username title: .item.name blueprint: '"gitlabMember"' properties: link: .item.web_url state: .item.state email: .item.email locked: .item.locked ``` * Include Bot Members * Include Inherited and Invited Members * Include Verbose Member Object GitLab allows the creation of tokens (bots) for automated tasks, which can be associated with groups or projects via access tokens. The `includeBotMembers` parameter is used to filter out bot members from the actual GitLab members. By default, this selector is set to `false`, which means the integration will only sync actual members. ``` - kind: group-with-members selector: query: 'true' includeBotMembers: false ``` ``` - kind: project-with-members selector: query: 'true' includeBotMembers: false ``` You can also specify the `includeInheritedMembers` selector to control the inclusion of inherited members in the member data. By default, this parameter is set to `false`, and the integration will sync only direct members without inherited members. ``` - kind: group-with-members selector: query: 'true' includeInheritedMembers: false ``` ``` - kind: project-with-members selector: query: 'true' includeInheritedMembers: false ``` By default, the integration syncs only essential member properties (id, username, and email) to optimize memory usage. To include all member properties available in the GitLab API (like name, state, web\_url, etc.), set the `includeVerboseMemberObject` selector to `true`. This gives you access to the complete member object but may increase memory consumption. If you must change this setting, we recommend you change the value of this selector only for the kind you intend to use for parsing members (not groups). ``` - kind: group-with-members selector: query: 'true' includeVerboseMemberObject: false ``` ``` - kind: project-with-members selector: query: 'true' includeVerboseMemberObject: false ``` ### Members and groups[​](#members-and-groups "Direct link to Members and groups") The following example demonstrates how to ingest your GitLab groups and their members to Port.
You can use the following Port blueprint definitions and integration configuration: **Group blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabGroup", "title": "Group", "icon": "GitLab", "schema": { "properties": { "visibility": { "icon": "Lock", "title": "Visibility", "type": "string", "description": "Visibility status of the group. (e.g public, internal etc. )", "enum": [ "public", "internal", "private" ], "enumColors": { "public": "red", "internal": "yellow", "private": "green" } }, "url": { "title": "URL", "format": "url", "type": "string", "icon": "Link", "description": "Link to the gitlab group" }, "description": { "title": "Description", "type": "string", "icon": "BlankPage", "description": "A short description of the gitlab group" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "gitlabMembers": { "title": "Gitlab Members", "target": "gitlabMember", "required": false, "many": true } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: group-with-members selector: query: 'true' includeVerboseMemberObject: 'true' port: itemsToParse: .__members entity: mappings: identifier: .item.username title: .item.name blueprint: '"gitlabMember"' properties: link: .item.web_url state: .item.state email: .item.email locked: .item.locked - kind: group-with-members selector: query: 'true' port: entity: mappings: identifier: .full_path title: .name blueprint: '"gitlabGroup"' properties: url: .web_url visibility: .visibility description: .description relations: gitlabMembers: .__members | map(.username) ``` ### Members and projects[​](#members-and-projects "Direct link to Members and projects") The following example demonstrates how to ingest your GitLab projects and their members to Port.
You can use the following Port blueprint definitions and integration configuration: Limitation Real time webhook events are not supported for the `project-with-members` kind. **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "gitlabMembers": { "title": "Gitlab Members", "target": "gitlabMember", "required": false, "many": true } } } ``` **Integration mapping (click to expand)** ``` resources: - kind: project-with-members selector: query: 'true' includeVerboseMemberObject: 'true' port: itemsToParse: .__members entity: mappings: identifier: .item.username title: .item.name blueprint: '"gitlabMember"' properties: link: .item.web_url state: .item.state email: .item.email locked: .item.locked - kind: project-with-members selector: query: 'true' port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"gitlabRepository"' properties: url: .web_url readme: file://README.md description: .description language: .__languages | to_entries | max_by(.value) | .key namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch relations: gitlabMembers: .__members | map(.username) ``` Learn more * Refer to the [setup](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md#setup) section to learn more about the integration configuration setup process. * We leverage [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform GitLab objects to Port entities. * Click [Here](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project) for the GitLab project or group member object structure. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The examples above show specific use cases, but Port's GitLab integration supports the ingestion of many other GitLab objects. To adapt the examples above, use the GitLab API reference to learn about the available fields for the different supported objects: Available GitLab resources The following resources can be used to map data from GitLab, it is possible to reference any field that appears in the API responses linked below for the GitLab integration mapping configuration. * [`group`](https://docs.gitlab.com/ee/api/groups.html#list-groups) * [`project`](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects) * [`merge-request`](https://docs.gitlab.com/ee/api/merge_requests.html#list-project-merge-requests) * [`pipeline`](https://docs.gitlab.com/ee/api/pipelines.html#list-project-pipelines) * [`job`](https://docs.gitlab.com/ee/api/jobs.html#list-project-jobs) * [`issue`](https://docs.gitlab.com/ee/api/issues.html#list-project-issues) * [`file`](https://docs.gitlab.com/ee/api/repository_files.html#get-file-from-repository) * [`group-with-members`](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project) * [`project-with-members`](https://docs.gitlab.com/ee/api/members.html#list-all-members-of-a-group-or-project) When adding the ingestion of other resources, remember to add an entry to the `resources` array and change the value provided to the `kind` key accordingly. After creating the blueprints and saving the integration configuration, you will see new entities in Port matching your projects alongside their issues. --- # GitOps Port's GitLab integration makes it possible to manage Port entities with a GitOps approach, making your code projects into the source of truth for the various infrastructure assets you want to manage. Apphost prerequisite When installing the Gitlab integration, the `appHost` parameter must be provided in order to use GitOps.
Without it, the integration will not be able to receive webhook events about pushes to the `port.yml` file. ## Common use cases[​](#common-use-cases "Direct link to Common use cases") * Use GitLab as the source-of-truth for your **microservices**, **projects**, **packages**, **libraries** and other software catalog assets. * Allow developers to keep the catalog up-to-date, by making updates to files in their Git projects. * Create a standardized way to document software catalog assets in your organization. ## Managing entities using GitOps[​](#managing-entities-using-gitops "Direct link to Managing entities using GitOps") To manage entities using GitOps, you will need to add a `port.yml` file to the **default branch** (usually `main`) of your project. The `port.yml` file can specify one or more Port entities that will be ingested to Port, and any change made to the `port.yml` file will also be reflected inside Port. This configuration turns your GitLab projects to the source-of-truth for the software catalog. ### GitOps `port.yml` file[​](#gitops-portyml-file "Direct link to gitops-portyml-file") The `port.yml` file is how you specify your Port entities that are managed using GitOps and whose data is ingested from your Git projects. Here are examples for valid `port.yml` files: * Single entity * Multiple entities ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ``` - identifier: myEntity1 title: My Entity1 blueprint: myFirstBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 - identifier: myEntity title: My Entity2 blueprint: mySecondBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ``` Since both of the valid `port.yml` formats follow the same structure, the following section will explain the format based on the single entity example. ### `port.yml` structure[​](#portyml-structure "Direct link to portyml-structure") Here is an example `port.yml` file: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` * The `identifier` key is used to specify the identifier of the entity that the app will create and keep up-to-date when changes occur: ``` identifier: myEntity title: My Entity ... ``` * The `title` key is used to specify the title of the entity: ``` identifier: myEntity title: My Entity ... ``` * The `blueprint` key is used to specify the identifier of the blueprint to create this entity from: ``` ... title: My Entity blueprint: myBlueprint ... ``` * The `properties` key is used to map the values to the different properties of the entity: ``` ... title: My Entity blueprint: myBlueprint properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com ... ``` * The `relations` key is used to map target entities to the different relations of the entity: * Single relation * Many relation ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: mySingleRelation: myTargetEntity ``` ``` ... properties: myStringProp: myValue myNumberProp: 5 myUrlProp: https://example.com relations: myManyRelation: - myTargetEntity1 - myTargetEntity2 ``` ### Setting null properties[​](#setting-null-properties "Direct link to Setting null properties") When you want to clear a property's value in Port, you can explicitly set it to `null` in your `port.yml` file.
For example: ``` identifier: myEntity title: My Entity blueprint: myBlueprint properties: description: null owner: null ``` Difference between null and omitting When you omit a property entirely from the `port.yml`, Port will keep its existing value. Setting a property to `null` explicitly tells Port to clear that property's value. ### Ingesting project file contents[​](#ingesting-project-file-contents "Direct link to Ingesting project file contents") It is possible to use the contents of files in the repository as the value for entity properties using a simple reference. The following example will read the string contents of `~/module1/README.md` and upload it to `myStringProp` of the specified entity. Repository folder structure used for the example: ``` root | +- port.yml | +-+ module1 | | | +- README.md | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: myStringProp: file://module1/README.md ``` #### Using relative paths[​](#using-relative-paths "Direct link to Using relative paths") It is also possible to use paths relative to the location of the `port.yml` spec file. For example: `file://./` is used to reference a file in the same directory as the `port.yml` file. `file://../` is used to reference a file that is one directory above and so on. The following example reads `README.md` and `module1/requirements.txt` using paths relative to `port.yml` Repository folder structure used for the example: ``` root | +-+ meta | | | +-- port.yml | | | +-+ README.md | +-+ module1 | | | +- requirements.txt | | | +-+ src ... ``` `port.yml` file: ``` blueprint: code_module title: Module 1 identifier: module_1_entity properties: readme: file://./README.md module1Requirements: file://../module1/requirements.txt ``` ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Port.yml ingestion pattern[​](#portyml-ingestion-pattern "Direct link to Port.yml ingestion pattern") The `port.yml` ingestion pattern allows you to configure the GitLab integration to ingest `port.yml` files as part of the resync process. This approach is particularly useful when you want to maintain data integrity and ensure that your `port.yml` files are properly synchronized with Port. `port.yml` files operate in the GitOps methodology, meaning they are ingested into Port whenever a commit to the main branch of the repository is detected. Here's how to configure the integration to parse and ingest `port.yml` files: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: entity: mappings: identifier: .file.content.identifier title: .file.content.title blueprint: .file.content.blueprint properties: property_a: .file.content.properties.property_a property_b: .file.content.properties.property_b relations: relation_a: .file.content.relations.relation_a relation_b: .file.content.relations.relation_b ``` For multiple entities in a single `port.yml` file, use the `itemsToParse` key: ``` enableMergeEntity: true resources: - kind: file selector: query: "true" files: - path: "**/port.yml" port: itemsToParse: .file.content entity: mappings: identifier: .item.identifier title: .item.title blueprint: .item.blueprint properties: property_a: .item.properties.property_a property_b: .item.properties.property_b relations: relation_a: .item.relations.relation_a relation_b: .item.relations.relation_b ``` ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/advanced/.md) page for advanced use cases and configurations. --- # Mapping Extensions ## Introduction[​](#introduction "Direct link to Introduction") The default way to map your data to Port is by using [JQ JSON processor](https://stedolan.github.io/jq/manual/) to map and transform your data to Port entities. However, in some cases you may want to map data to Port in a way that default JQ mapping is not enough. Possible Use Cases: * Map your repository README.md file contents into Port. * Check if a specific file exists in your repository. * Check if a specific string exists in your repository. * Check if a specific version of a package is used in your repository. * Check if a CI/CD pipeline is configured in your repository. ## Mapping file content into Port[​](#mapping-file-content-into-port "Direct link to Mapping file content into Port") The following example demonstrates how to define and export your GitLab projects and their **README.md** file contents to Port: **Repository blueprint (click to expand)** Create in Port ``` { "identifier": "gitlabRepository", "title": "Repository", "icon": "Microservice", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "readme": { "title": "README", "type": "string", "format": "markdown" }, "description": { "title": "Description", "type": "string" }, "language": { "title": "Language", "type": "string" }, "namespace": { "title": "Namespace", "type": "string" }, "fullPath": { "title": "Full Path", "type": "string" }, "defaultBranch": { "title": "Default Branch", "type": "string" }, "labels": { "items": { "type": "object" }, "type": "array", "title": "Labels" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` As we can see one of the properties is of type markdown, this means that we need to map the **README.md** file contents into Port. To do so, we will use the `file://` prefix with the path of the file to tell the GitLab exporter that we want to map the contents of a file into Port. ``` - kind: project selector: query: "true" port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"gitlabRepository"' properties: url: .web_url readme: file://README.md description: .description namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch ``` ## Search checks[​](#search-checks "Direct link to Search checks") We can use the GitLab exporter to perform search checks on our repositories to ensure that they are compliant with our organization's policies and standards. With Port Scorecards and the search checks feature, organization can define a set of checks that will be performed on each repository, and the results of those checks will be reflected in the repository's scorecard in Port which will help the organization to identify repositories that are not compliant with the organization's policies and standards. ### How it works[​](#how-it-works "Direct link to How it works") The search checks are performed using the [GitLab Advanced Search API](https://docs.gitlab.com/ee/api/search.html). This means that any search query supported by the GitLab Search API can be used with the GitLab exporter. ### Syntax[​](#syntax "Direct link to Syntax") * `search://scope=&&query=` * `search://` - This is the prefix to indicate that the mapping is a search check. * `scope` - The scope of the search, currently only `blobs` are supported (searches in the repository files) (see [GitLab Search API](https://docs.gitlab.com/ee/api/search.html#scope) for more details). * `query` - The query to search for, expected to be a valid GitLab Advanced Search query (see [GitLab Advanced Search syntax](https://docs.gitlab.com/ee/user/search/advanced_search.html#syntax) for more details). ### Search Checks Examples[​](#search-checks-examples "Direct link to Search Checks Examples") * `search://scope=blobs&&query=filename:README.md` - Check whether the project contains a `README.md` file. * `search://scope=blobs&&query=filename:test_* extension:py` - Check whether the project contains tests. * `search://scope=blobs&&query=filename:".gitlab-ci.yml` - Check whether the project has gitlab CI configured. * `search://scope=blobs&&query=fastapi filename:requirements.txt` - Check whether the project uses fastapi in the `requirements.txt` file. In the screenshot below we can see that we use the exact same query as gitlab advanced search ![GitLab advanced search for FastAPI in requirements.txt returning a matching code result](/assets/images/GitlabSearchQueryExample-9381cdf4324dd938ea70fb63820daae7.png) ### Integration Config Example[​](#integration-config-example "Direct link to Integration Config Example") ``` - kind: project selector: query: 'true' port: entity: mappings: identifier: .path_with_namespace | gsub(" "; "") title: .name blueprint: '"gitlabRepository"' properties: url: .web_link description: .description namespace: .namespace.name fullPath: .namespace.full_path defaultBranch: .default_branch readme: file://README.md hasLicense: search://scope=blobs&&query=filename:"LICENSE" usingFastapiPackage: search://scope=blobs&&query=fastapi filename:requirements.txt hasCI: search://scope=blobs&&query=filename:.gitlab-ci.yml usingOldLoggingPackage: search://scope=blobs&&query=logging extension:py hasTests: search://scope=blobs&&query=filename:test_* extension:py ``` Example of a Scorecard that reflects the results of the search checks: ![Best App Code Maturity scorecard passing README, CI, and test checks](/assets/images/GitlabSearchScorecardExample-a7e03a6eb54bcdc9d55e31fdf9e94c36.png) --- # GitOps using Port CRDs You can use GitOps, [Port's K8s exporter](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) and [Port's Entity CRDs](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/port-crd/.md) to export custom entities in to Port. Prerequisites: K8s exporter and Port CRDs For fully understanding how to use GitOps with Port's CRDs to map entities, make sure you are familiar with: * [Ports Kubernetes exporter](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) * [Ports Entity CRDs](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/port-crd/.md) ## Export entities with GitOps and Kubernetes use cases[​](#export-entities-with-gitops-and-kubernetes-use-cases "Direct link to Export entities with GitOps and Kubernetes use cases") * Use your K8s clusters as the source-of-truth for your **microservices**, **packages**, **libraries** and other software catalog assets. * Update Port in a "Push Only" method, where no elevated permissions are required for Port to interact with your infrastructure. * Allow developers to keep the catalog up-to-date, by making updates to Kubernetes manifest files in their Git repositories. * Create a standardized way to document software catalog assets in your organization. ## Managing entities defined using CRDs and GitOps[​](#managing-entities-defined-using-crds-and-gitops "Direct link to Managing entities defined using CRDs and GitOps") Port's CRDs allow defining and mapping any kind of entity using Kubernetes. You can find an example for doing this in the [Port CRDs - mapping a microservice example](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/port-crd/.md#example---mapping-a-microservice-using-port-crds). Mapping entities can be done using any Continuous Deployment (CD) solution, for example ArgoCD or FluxCD, by deploying the Port custom resources using the CD solution, and mapping their definition to Port using Port's K8s exporter. To achieve this, follow these general steps for any CD solution: 1. Navigate to [Port's CRDs document](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/port-crd/.md#deploying-ports-crds) page to learn how to deploy the CRDs. You can deploy them as part of your CD pipeline by placing the CRD manifests in one of your CD source directory/applications. 2. Define a Kubernetes Port entity manifest using Port's CRD, which contains the data model and data you wish to map to Port, and deploy it to your kubernetes cluster using your CD solution. 3. [Update](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md#updating-exporter-configuration) Port's K8s exporter resource mapping to map the Port CRD you just created. The entities defined using Port's CRD will appear in your Port environment. --- # Working with monorepos
After [connecting your Git provider](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/.md) to Port, Port will automatically create an [entity]() for each repository in the organization in which you installed the integration. If you're working with a **monorepo** and would like to create an entity for each microservice in a single repository instead, you can do that by making an adjustment to your Git integration's mapping: 1. Go to the [connectors page](https://app.port.io/settings/data-sources) of your portal. 2. Under `Exporters`, click on the Git provider you would like to edit, for example: ![GitHub exporter connectors panel](/img/sync-data-to-catalog/monorepoDataSourcesExample.png)

3. A window will open with a YAML mapping of the integration.
Use the following snippet as needed for your use-case (either add the `folder` entry to the `resources` array, or replace the entire YAML with it): Choose repos and folders to include In the snippet below, change the `path` and `repos` fields to your desired values before copying. * GitHub * GitLab * BitBucket * Azure DevOps (coming soon) If you want to ingest a `README.md` for each folder (for example `apps/service-a/README.md`), use **Option A** below.
If you want to ingest the repository's root `README.md` and attach it to every folder entity, use **Option B**. **Option A — folder README (resolved relative to each matched folder path):** ``` resources: - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: apps/* # Relative path to the folders within the repositories. includedFiles: # For GitHub Ocean folder entities, includedFiles declared here are resolved per folder # (e.g. apps/service-a/README.md) and attached under .folder.__includedFiles - README.md repos: # List of repositories to include folders from. - backend-service - frontend-service port: entity: mappings: identifier: ".folder.name" title: ".folder.name" blueprint: '"githubRepository"' properties: url: .repo.html_url + "/tree/" + .repo.default_branch + "/" + .folder.path readme: .folder.__includedFiles["README.md"] ``` **Option B — repository root README (same README for all folders):** ``` resources: - kind: folder selector: query: "true" folders: - path: apps/* repos: - backend-service - frontend-service includedFiles: # These paths are fetched from the repository root and attached under .__includedFiles - README.md port: entity: mappings: identifier: ".folder.name" title: ".folder.name" blueprint: '"githubRepository"' properties: url: .repo.html_url + "/tree/" + .repo.default_branch + "/" + .folder.path readme: .__includedFiles["README.md"] ``` ``` resources: - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: "apps/" # Relative path to the folders within the repositories. repos: # List of repositories to include folders from (use path_with_namespace). - group/backend-service - group/frontend-service includedFiles: - README.md port: entity: mappings: identifier: .folder.name title: .folder.name blueprint: '"service"' properties: url: >- .repo.web_url + "/tree/" + .repo.default_branch + "/" + .folder.path language: .repo.__languages | to_entries | max_by(.value) | .key readme: .__includedFiles["README.md"] ``` ``` resources: - kind: folder selector: query: "true" # JQ boolean query. If evaluated to false - skip syncing the object. folders: # Specify the repositories and folders to include under this relative path. - path: apps/* # Relative path to the folders within the repositories. repos: # List of repositories to include folders from. - backend-service - frontend-service includedFiles: - README.md port: entity: mappings: identifier: .folder.name blueprint: '"bitbucketRepository"' properties: url: .repo.links.html.href + "/src/" + .repo.mainbranch.name + "/" + .folder.path readme: .__includedFiles["README.md"] ``` Azure DevOps support Currently monorepo support is available for the Git providers listed below.
Support for Azure DevOps is coming soon. Stay tuned! 4. Click on `Resync` to apply the changes. 5. Head back to your [Software catalog](https://app.port.io/organization/catalog), as you can see Port has now created an entity for each folder in the specified repositories, instead of creating an entity for each repository. --- # Microsoft Entra ID Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's Microsoft Entra ID (formerly Azure AD) integration allows you to model Entra ID resources in your software catalog and ingest data into them using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The Microsoft Entra ID integration can ingest the following resources into Port: * `entra-id-user` - User accounts and their profile information from [`/users`](https://learn.microsoft.com/en-us/graph/api/user-list). * `entra-id-group` - Security and Microsoft 365 groups from [`/groups`](https://learn.microsoft.com/en-us/graph/api/group-list). * `entra-id-application` - Enterprise applications registered in Entra ID from [`/applications`](https://learn.microsoft.com/en-us/graph/api/application-list). * `entra-id-service-principal` - Service principal objects representing applications from [`/servicePrincipals`](https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list). It is possible to reference any field that appears in the API responses linked above in the mapping configuration. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, you need: * A Microsoft Entra ID application registration with appropriate API permissions. * A client secret for the application. * Admin consent for the required permissions. * An access token (bearer token) for Microsoft Graph API. **To register an application in Microsoft Entra ID:** 1. Log in to the [Azure Portal](https://portal.azure.com). 2. Navigate to **Microsoft Entra ID** > **App registrations**. 3. Click **New registration**. 4. Provide a name for your application (e.g., "Port Integration"). 5. Select **Accounts in this organizational directory only**. 6. Click **Register**. 7. Note the **Application (client) ID** and **Directory (tenant) ID**. **To create a client secret:** 1. In your app registration, navigate to **Certificates & secrets**. 2. Click **New client secret**. 3. Provide a description (e.g., "Port Integration Secret"). 4. Select an expiration period. 5. Click **Add**. 6. **Copy the secret value immediately** - it will not be shown again. Secret security Store your client secret securely and never share it. The secret provides access to your Entra ID data. **To grant API permissions:** 1. In your app registration, navigate to **API permissions**. 2. Click **Add a permission**. 3. Select **Microsoft Graph**. 4. Select **Application permissions**. 5. Add the following permissions: * `User.Read.All` - Read all users' full profiles. * `Group.Read.All` - Read all groups. * `Application.Read.All` - Read all applications. * `Directory.Read.All` - Read directory data. 6. Click **Add permissions**. 7. Click **Grant admin consent for \[Your Organization]**. Admin consent required An Entra ID administrator must grant consent for these permissions before the integration can access the data. **To get an access token (bearer token):** You need to obtain an OAuth2 access token to authenticate with Microsoft Graph API. Use the following curl command to get a token: ``` curl -X POST "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "scope=https://graph.microsoft.com/.default" \ -d "grant_type=client_credentials" | jq -r '.access_token' ``` Replace: * `YOUR_TENANT_ID` with your Directory (tenant) ID. * `YOUR_CLIENT_ID` with your Application (client) ID. * `YOUR_CLIENT_SECRET` with your client secret value. Token expiration Access tokens typically expire after 1 hour. For production use, consider implementing automatic token refresh or using OAuth2 client credentials flow with automatic token management. **To find your tenant ID:** Your tenant ID is the **Directory (tenant) ID** from your app registration. You can also find it in the Microsoft Entra ID overview page. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. Configure: * **Identifier**: `entra-id-integration`. * **Base URL**: `https://graph.microsoft.com/v1.0`. * **Authentication type**: `bearer_token`. * **Token**: your Microsoft Graph API bearer token (see the Prerequisites section for how to generate it). 3. Expand **Advanced configuration** and set: * **Pagination type**: `next_link`. 4. Click **Save & install**. Continue with the mappings After the integration is installed, continue to the **Configuration** section on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation 1. Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_BEARER_TOKEN`. Get your bearer token using the curl command in the Prerequisites section. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-entra-id-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set scheduledResyncInterval=120 \ --set integration.identifier="entra-id-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://graph.microsoft.com/v1.0" \ --set integration.config.authType="bearer_token" \ --set integration.config.apiToken="YOUR_BEARER_TOKEN" \ --set integration.config.paginationType="next_link" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters This table summarizes the available parameters for the installation. | Parameter | Description | Example | Required | | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | -------- | | `port.clientId` | Your Port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials). | | ✅ | | `port.baseUrl` | Your Port API URL (`https://api.port.io` for EU, `https://api.us.port.io` for US). | | ✅ | | `integration.config.baseUrl` | Base URL of the Microsoft Graph API. | | ✅ | | `integration.config.authType` | Authentication type for the API (use `bearer_token` for Entra ID). | bearer\_token | ✅ | | `integration.config.apiToken` | Microsoft Graph API bearer token created via the OAuth2 client credentials flow. | eyJ0eXAiOiJKV1QiLCJub25jZSI6... | ✅ | | `integration.config.paginationType` | How the API handles pagination (offset, page, cursor, next\_link, or none). | next\_link | ❌ | | `integration.eventListener.type` | Event listener type. See [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md). | POLLING | ✅ | | `integration.type` | Integration type (must be `custom`). | custom | ✅ | | `integration.identifier` | Unique identifier for the integration instance. | entra-id-integration | ✅ | | `scheduledResyncInterval` | Minutes between scheduled syncs. When omitted, the event listener interval is used. | 120 | ❌ | | `initializePortResources` | When true, creates default blueprints and mappings on first run. | true | ❌ | | `sendRawDataExamples` | Sends sample payloads from the API to Port for easier mapping. | true | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_BEARER_TOKEN`. Get your bearer token using the curl command in the Prerequisites section. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://graph.microsoft.com/v1.0" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="YOUR_BEARER_TOKEN" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="next_link" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before the integration can sync data, you need to create the required blueprints in Port. These blueprints define the data model for your Entra ID resources. **To create the blueprints:** Create the blueprints in the following order: 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on the `+ Blueprint` button. 3. Copy and paste each blueprint JSON from the sections below in order: **1. Entra ID Application Blueprint (click to expand)** Enterprise applications registered in Entra ID: Create in Port ``` { "identifier": "entra-id-application", "description": "An application registration in Microsoft Entra ID", "title": "Entra ID Application", "icon": "Microsoft", "schema": { "properties": { "displayName": { "title": "Display Name", "type": "string" }, "appId": { "title": "Application ID", "type": "string" }, "publisherDomain": { "title": "Publisher Domain", "type": "string" }, "signInAudience": { "title": "Sign-In Audience", "type": "string" }, "createdDateTime": { "title": "Created Date", "type": "string", "format": "date-time" } }, "required": ["displayName"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **2. Entra ID Service Principal Blueprint (click to expand)** Service principal objects representing applications: Create in Port ``` { "identifier": "entra-id-service-principal", "description": "A service principal object in Microsoft Entra ID", "title": "Entra ID Service Principal", "icon": "Microsoft", "schema": { "properties": { "displayName": { "title": "Display Name", "type": "string" }, "appId": { "title": "Application ID", "type": "string" }, "appOwnerOrganizationId": { "title": "Owner Organization", "type": "string" }, "accountEnabled": { "title": "Account Enabled", "type": "boolean" }, "appRoleAssignmentRequired": { "title": "App Role Assignment Required", "type": "boolean" }, "createdDateTime": { "title": "Created Date", "type": "string", "format": "date-time" } }, "required": ["displayName"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **3. Entra ID User Blueprint (click to expand)** User accounts and their profile information: Create in Port ``` { "identifier": "entra-id-user", "description": "A Microsoft Entra ID user account", "title": "Entra ID User", "icon": "Microsoft", "schema": { "properties": { "userPrincipalName": { "title": "User Principal Name", "type": "string", "format": "email" }, "displayName": { "title": "Display Name", "type": "string" }, "givenName": { "title": "Given Name", "type": "string" }, "surname": { "title": "Surname", "type": "string" }, "mail": { "title": "Email", "type": "string", "format": "email" }, "jobTitle": { "title": "Job Title", "type": "string" }, "department": { "title": "Department", "type": "string" }, "officeLocation": { "title": "Office Location", "type": "string" }, "accountEnabled": { "title": "Account Enabled", "type": "boolean" }, "createdDateTime": { "title": "Created Date", "type": "string", "format": "date-time" }, "lastSignInDateTime": { "title": "Last Sign In", "type": "string", "format": "date-time" } }, "required": ["userPrincipalName"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **4. Entra ID Group Blueprint (click to expand)** Security and Microsoft 365 groups: Create in Port ``` { "identifier": "entra-id-group", "description": "A Microsoft Entra ID group (security or Microsoft 365)", "title": "Entra ID Group", "icon": "Microsoft", "schema": { "properties": { "displayName": { "title": "Display Name", "type": "string" }, "description": { "title": "Description", "type": "string" }, "groupTypes": { "title": "Group Types", "type": "array", "items": { "type": "string" } }, "mailEnabled": { "title": "Mail Enabled", "type": "boolean" }, "securityEnabled": { "title": "Security Enabled", "type": "boolean" }, "createdDateTime": { "title": "Created Date", "type": "string", "format": "date-time" } }, "required": ["displayName"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "members": { "title": "Members", "target": "entra-id-user", "required": false, "many": true } } } ``` 4. Click `Save` to save each blueprint after creating it. Adding relations after creation After creating both the User and Group blueprints, you can edit the User blueprint to add a relation to Group. In the User blueprint, add this to the `relations` section: ``` "groups": { "title": "Groups", "target": "entra-id-group", "required": false, "many": true } ``` ## Configuration[​](#configuration "Direct link to Configuration") After installation, define which endpoints to sync in your integration configuration. Each resource maps an API endpoint to Port entities using [JQ expressions](https://stedolan.github.io/jq/manual/) to transform the data. **Key mapping components:** * **`kind`**: The API endpoint path (combined with your base URL). * **`selector.query`**: JQ filter to include/exclude entities (use `'true'` to sync all). * **`selector.data_path`**: JQ expression pointing to the array of items in the response. * **`port.entity.mappings`**: How to map API fields to Port entity properties. For more details on how the no-code Ocean custom integration works, see the [How it works](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview#how-it-works) section in the custom integration overview. **Microsoft Graph API response format:** Microsoft Graph API responses typically follow this structure: ``` { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", "value": [ { "id": "00000000-0000-0000-0000-000000000000", "displayName": "John Doe", ... } ], "@odata.nextLink": "https://graph.microsoft.com/v1.0/users?$skiptoken=..." } ``` The actual data array is in the `.value` property, and pagination information is in `@odata.nextLink` with a `$skiptoken` parameter. **To configure the mappings:** 1. Go to your [connectors page](https://app.port.io/settings/data-sources). 2. Find your Entra ID integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below. **Users mapping (click to expand)** ``` resources: - kind: /users selector: query: 'true' data_path: '.value' query_params: $top: "999" port: entity: mappings: identifier: .id title: .displayName // .userPrincipalName blueprint: '"entra-id-user"' properties: userPrincipalName: .userPrincipalName displayName: .displayName givenName: .givenName surname: .surname mail: .mail jobTitle: .jobTitle department: .department officeLocation: .officeLocation accountEnabled: .accountEnabled createdDateTime: .createdDateTime lastSignInDateTime: .signInActivity.lastSignInDateTime ``` **Groups mapping (click to expand)** ``` resources: - kind: /groups selector: query: 'true' data_path: '.value' query_params: $top: "999" $expand: "members($select=id)" port: entity: mappings: identifier: .id title: .displayName blueprint: '"entra-id-group"' properties: displayName: .displayName description: .description groupTypes: .groupTypes mailEnabled: .mailEnabled securityEnabled: .securityEnabled createdDateTime: .createdDateTime relations: members: "[.members[]?.id]" ``` **Applications mapping (click to expand)** ``` resources: - kind: /applications selector: query: 'true' data_path: '.value' query_params: $top: "999" port: entity: mappings: identifier: .id title: .displayName blueprint: '"entra-id-application"' properties: displayName: .displayName appId: .appId publisherDomain: .publisherDomain signInAudience: .signInAudience createdDateTime: .createdDateTime ``` **Service principals mapping (click to expand)** ``` resources: - kind: /servicePrincipals selector: query: 'true' data_path: '.value' query_params: $top: "999" port: entity: mappings: identifier: .id title: .displayName blueprint: '"entra-id-service-principal"' properties: displayName: .displayName appId: .appId appOwnerOrganizationId: .appOwnerOrganizationId accountEnabled: .accountEnabled appRoleAssignmentRequired: .appRoleAssignmentRequired createdDateTime: .createdDateTime ``` 5. Click `Save` to save the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to customize your setup or test different API endpoints before committing to a configuration, use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md). **The interactive builder helps you:** 1. Test your Microsoft Graph API endpoints with live data. 2. Automatically detect the data structure and field types. 3. Generate blueprints and resource mappings tailored to your preferences. 4. Get installation commands with your configuration pre-filled. Simply provide your Entra ID API details, and the builder will generate everything you need to install and create the integration in Port. --- # Okta ![Okta icon](/img/guides/icons/Okta.svg)![Okta icon](/img/guides/icons/dark/Okta.svg) Loading version... Port's Okta integration allows you to model Okta identity and access management resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[User](https://developer.okta.com/docs/reference/api/users/)[Group](https://developer.okta.com/docs/reference/api/groups/) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### Create an Okta API token[​](#create-an-okta-api-token "Direct link to Create an Okta API token") 1. Log in to your Okta admin console. 2. Navigate to **Security** > **API** > **Tokens**. 3. Click **Create Token**. 4. Provide a name for your token (e.g., "Port Integration"). 5. Click **Create Token**. 6. Copy the generated token and save it securely. Token Security Store your API token securely and never share it. The token provides access to your Okta data. ### Okta Domain[​](#okta-domain "Direct link to Okta Domain") Your Okta domain is the subdomain of your Okta organization URL. For example, if your Okta URL is `https://dev-123456.okta.com`, your domain would be `dev-123456.okta.com`. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` resources: - kind: okta-user selector: query: 'true' include_groups: true include_applications: true fields: "id,status,created,activated,lastLogin,lastUpdated,profile:(login,firstName,lastName,displayName,email,title,department,employeeNumber,mobilePhone,primaryPhone,streetAddress,city,state,zipCode,countryCode)" port: entity: mappings: identifier: .id title: .profile.displayName // .profile.firstName + " " + .profile.lastName // .profile.login blueprint: '"okta-user"' properties: login: .profile.login email: .profile.email firstName: .profile.firstName lastName: .profile.lastName displayName: .profile.displayName title: .profile.title department: .profile.department employeeNumber: .profile.employeeNumber mobilePhone: .profile.mobilePhone primaryPhone: .profile.primaryPhone streetAddress: .profile.streetAddress city: .profile.city state: .profile.state zipCode: .profile.zipCode countryCode: .profile.countryCode status: .status created: .created activated: .activated lastLogin: .lastLogin lastUpdated: .lastUpdated relations: groups: .groups[]?.id - kind: okta-group selector: query: 'true' port: entity: mappings: identifier: .id title: .profile.name blueprint: '"okta-group"' properties: name: .profile.name description: .profile.description type: .type created: .created lastUpdated: .lastUpdated relations: members: .users[]?.id ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to 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 [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Additional examples of blueprints and the relevant integration configurations: UserGroup BlueprintJSON Create in Port ``` { "identifier": "okta-user", "title": "Okta User", "icon": "Okta", "schema": { "properties": { "login": { "title": "Login", "type": "string" }, "email": { "title": "Email", "type": "string", "format": "email" }, "firstName": { "title": "First Name", "type": "string" }, "lastName": { "title": "Last Name", "type": "string" }, "displayName": { "title": "Display Name", "type": "string" }, "title": { "title": "Job Title", "type": "string" }, "department": { "title": "Department", "type": "string" }, "employeeNumber": { "title": "Employee Number", "type": "string" }, "mobilePhone": { "title": "Mobile Phone", "type": "string" }, "primaryPhone": { "title": "Primary Phone", "type": "string" }, "streetAddress": { "title": "Street Address", "type": "string" }, "city": { "title": "City", "type": "string" }, "state": { "title": "State", "type": "string" }, "zipCode": { "title": "ZIP Code", "type": "string" }, "countryCode": { "title": "Country Code", "type": "string" }, "status": { "title": "Status", "type": "string", "enum": ["ACTIVE", "INACTIVE", "PASSWORD_EXPIRED", "LOCKED_OUT", "SUSPENDED", "DEPROVISIONED"] }, "created": { "title": "Created Date", "type": "string", "format": "date-time" }, "activated": { "title": "Activated Date", "type": "string", "format": "date-time" }, "lastLogin": { "title": "Last Login", "type": "string", "format": "date-time" }, "lastUpdated": { "title": "Last Updated", "type": "string", "format": "date-time" } }, "required": ["login", "email"] }, "calculationProperties": {}, "relations": { "groups": { "title": "Groups", "target": "okta-group", "required": false, "many": true } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: okta-user selector: query: "true" include_groups: true include_applications: true port: entity: mappings: identifier: .id title: .profile.displayName // .profile.firstName + " " + .profile.lastName // .profile.login blueprint: '"okta-user"' properties: login: .profile.login email: .profile.email firstName: .profile.firstName lastName: .profile.lastName displayName: .profile.displayName title: .profile.title department: .profile.department employeeNumber: .profile.employeeNumber mobilePhone: .profile.mobilePhone primaryPhone: .profile.primaryPhone streetAddress: .profile.streetAddress city: .profile.city state: .profile.state zipCode: .profile.zipCode countryCode: .profile.countryCode status: .status created: .created activated: .activated lastLogin: .lastLogin lastUpdated: .lastUpdated relations: groups: .groups[]?.id ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `include_groups` | `boolean` | Include user groups in the response Default: `true`. | | `include_applications` | `boolean` | Include user applications in the response Default: `true`. | | `fields` | `string` | Comma-separated list of user fields to retrieve. Profile attributes should be contained within a profile:(field1,field2,...) directive. Default: `"id,status,created,activated,lastLogin,lastUpdated,profile:(login,firstName,lastName,displayName,email,title,department,employeeNumber,mobilePhone,primaryPhone,streetAddress,city,state,zipCode,countryCode,managerId,manager)"`. | BlueprintJSON Create in Port ``` { "identifier": "okta-group", "title": "Okta Group", "icon": "Okta", "schema": { "properties": { "name": { "title": "Name", "type": "string" }, "description": { "title": "Description", "type": "string" }, "type": { "title": "Type", "type": "string", "enum": ["BUILT_IN", "OKTA_GROUP", "APP_GROUP"] }, "created": { "title": "Created Date", "type": "string", "format": "date-time" }, "lastUpdated": { "title": "Last Updated", "type": "string", "format": "date-time" } }, "required": ["name"] }, "calculationProperties": {}, "relations": { "members": { "title": "Members", "target": "okta-user", "required": false, "many": true } } } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: okta-group selector: query: 'true' port: entity: mappings: identifier: .id title: .profile.name blueprint: '"okta-group"' properties: name: .profile.name description: .profile.description type: .type created: .created lastUpdated: .lastUpdated relations: members: .users[]?.id ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Okta 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 Okta 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Okta is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Okta 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Webhook configuration[​](#webhook-configuration "Direct link to Webhook configuration") The Okta integration supports real-time updates through webhooks. When using the self-hosted installation method, the integration will automatically: 1. Create an Event Hook in your Okta organization 2. Configure the webhook to send user and group change events 3. Process incoming webhook events to update Port entities in real-time ### Webhook events[​](#webhook-events "Direct link to Webhook events") The integration listens for the following Okta events: * `user.lifecycle.create` * `user.lifecycle.activate` * `user.lifecycle.deactivate` * `user.lifecycle.suspend` * `user.lifecycle.unsuspend` * `user.lifecycle.delete` * `user.account.update_profile` * `group.user_membership.add` * `group.user_membership.remove` * `group.lifecycle.create` * `group.lifecycle.delete` * `group.lifecycle.update` ## Limitations[​](#limitations "Direct link to Limitations") * The integration currently supports users and groups only * Custom Okta attributes are not automatically mapped but can be added to the configuration * Webhook verification is optional but recommended for production environments ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Common Issues[​](#common-issues "Direct link to Common Issues") 1. **Authentication Errors**: Verify your Okta API token has the correct permissions and is not expired 2. **Domain Issues**: Ensure your Okta domain is correctly formatted (e.g., `dev-123456.okta.com`) 3. **Webhook Failures**: Check that your self-hosted integration is accessible from the internet for webhook delivery 4. **Rate Limiting**: Okta has API rate limits; the integration handles this automatically with retries --- # Incident management Port's incident management integrations make it easy to keep your software catalog up to date based on information stored in your preferred incident management tool. For more information, check out the documentation for your respective tool: ## [📄️Incident.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/incident-io/.md) [Learn how to ingest Incident.io incidents, alerts, schedules, and teams into Port using the no-code Ocean custom integration framework.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/incident-io/.md) ## [🗃Statuspage](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/.md) ## [📄️FireHydrant](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/firehydrant/.md) [Sync FireHydrant incidents, services, and on-call data into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/firehydrant/.md) ## [🗃Opsgenie](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/.md) ## [🗃Pagerduty](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/.md) ## [📄️ServiceNow](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/servicenow/.md) [Sync ServiceNow incidents, services, and CMDB data into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/servicenow/.md) ## [📄️Rootly](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/rootly/.md) [Learn how to ingest Rootly incidents, services, teams, and on-call data into Port using the no-code Ocean custom integration framework.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/rootly/.md) ## [📄️Jira Service Management](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/jira-service-management/.md) [Learn how to ingest Jira Service Management service desks, customer requests, and organizations into Port using the no-code Ocean custom integration.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/jira-service-management/.md) --- # FireHydrant ![FireHydrant icon](/img/guides/icons/FireHydrant.svg)![FireHydrant icon](/img/guides/icons/FireHydrant.svg) Loading version... Port's FireHydrant integration allows you to model FireHydrant resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Environment](https://developers.firehydrant.com/#/operations/getV1Environments)[Service](https://developers.firehydrant.com/#/operations/getV1Services)[Incident](https://developers.firehydrant.com/#/operations/getV1Incidents)[Retrospective](https://developers.firehydrant.com/#/operations/getV1PostMortemsReports) ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: environment selector: query: 'true' port: entity: mappings: blueprint: '"firehydrantEnvironment"' identifier: .id title: .name properties: description: .description activeIncidents: .active_incidents | length createdAt: .created_at updatedAt: .updated_at - kind: service selector: query: 'true' port: entity: mappings: blueprint: '"firehydrantService"' identifier: .id title: .name properties: description: .description slug: .slug links: .links[].href_url labels: .labels owner: .owner.name activeIncidents: .active_incidents | length meanTimeToAcknowledge: '[(.__incidents.milestones[] | map(select(.type == "started" or .type == "acknowledged")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null)] | add / length / 3600 | floor' meanTimeToDetect: '[(.__incidents.milestones[] | map(select(.type == "started" or .type == "detected")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null)] | add / length / 3600 | floor' meanTimeToMitigate: '[(.__incidents.milestones[] | map(select(.type == "started" or .type == "mitigated")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null)] | add / length / 3600 | floor' meanTimeToResolve: '[(.__incidents.milestones[] | map(select(.type == "started" or .type == "resolved")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null)] | add / length / 3600 | floor' createdAt: .created_at updatedAt: .updated_at - kind: incident selector: query: 'true' port: entity: mappings: blueprint: '"firehydrantIncident"' identifier: .id title: .name properties: url: .incident_url priority: .priority severity: .severity tags: .tag_list currentMilestone: .current_milestone functionalities: .functionalities[].name description: .description customerImpact: .customers_impacted commander: .role_assignments[] | select(.incident_role.name == "Commander") | .user.email createdBy: .created_by.email createdAt: .created_at relations: environment: .environments | map(.id) service: .services | map(.id) - kind: retrospective selector: query: .incident.current_milestone == "postmortem_completed" port: entity: mappings: blueprint: '"firehydrantRetrospective"' identifier: .id title: .name properties: url: .incident.incident_url tags: .tag_list services: .incident.services[].name environments: .incident.environments[].name functionalities: .incident.functionalities[].name description: .incident.description customerImpact: .incident.customers_impacted commander: .incident.role_assignments[] | select(.incident_role.name == "Commander") | .user.email createdBy: .incident.created_by.email resolvedAt: .incident.milestones[] | select(.type == "resolved") | .created_at createdAt: .incident.created_at publishedAt: .incident.milestones[] | select(.type == "postmortem_completed") | .created_at duration: (.incident.milestones | map(select(.type == "started" or .type == "resolved")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null) | ./3600 | floor completedTasks: .__incident.tasks | map(select(.state == "done")) | length incompletedTasks: .__incident.tasks | map(select(.state != "done")) | length questions: '.questions | map({question: .title, answer: .body})' relations: incident: .incident.id ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") EnvironmentServiceIncidentRetrospective Default mappingYAML ``` - kind: environment selector: query: 'true' port: entity: mappings: blueprint: '"firehydrantEnvironment"' identifier: .id title: .name properties: description: .description activeIncidents: .active_incidents | length createdAt: .created_at updatedAt: .updated_at ``` Sample payloadJSON ``` { "id": "21011924-215e-4aa4-abbf-6155ecf237ae", "name": "Production", "slug": "production", "description": "", "updated_at": "2023-09-05T17:01:48.348Z", "created_at": "2023-09-05T17:01:48.348Z", "active_incidents": [ "71bdfd6f-4ee7-4222-a9a3-832637174ae7", "fcd05e8b-e66b-46b7-9210-d6607fdc8894" ], "external_resources": [] } ``` Port entityJSON ``` { "identifier": "21011924-215e-4aa4-abbf-6155ecf237ae", "title": "Production", "icon": "FireHydrant", "blueprint": "firehydrantEnvironment", "team": [], "properties": { "description": "", "activeIncidents": 2, "createdAt": "2023-09-05T17:01:48.348Z", "updatedAt": "2023-09-05T17:01:48.348Z" }, "relations": {}, "createdAt": "2023-11-01T11:42:52.458Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-11-01T11:42:52.458Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: service selector: query: 'true' port: entity: mappings: blueprint: '"firehydrantService"' identifier: .id title: .name properties: description: .description slug: .slug links: .links[].href_url labels: .labels owner: .owner.name activeIncidents: .active_incidents | length meanTimeToAcknowledge: '[(.__incidents.milestones[] | map(select(.type == "started" or .type == "acknowledged")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null)] | add / length / 3600 | floor' meanTimeToDetect: '[(.__incidents.milestones[] | map(select(.type == "started" or .type == "detected")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null)] | add / length / 3600 | floor' meanTimeToMitigate: '[(.__incidents.milestones[] | map(select(.type == "started" or .type == "mitigated")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null)] | add / length / 3600 | floor' meanTimeToResolve: '[(.__incidents.milestones[] | map(select(.type == "started" or .type == "resolved")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null)] | add / length / 3600 | floor' createdAt: .created_at updatedAt: .updated_at ``` Sample payloadJSON ``` { "id": "87aeea3d-4dcd-4c1e-bf8c-e68e73892e44", "name": "My Test Service", "description": "For testing this service", "slug": "my-test-service", "service_tier": 3, "created_at": "2023-09-06T12:37:37.378Z", "updated_at": "2023-09-20T10:25:32.730Z", "labels": { "myket": "service", "keyval": "app" }, "active_incidents": [ "71bdfd6f-4ee7-4222-a9a3-832637174ae7", "fcd05e8b-e66b-46b7-9210-d6607fdc8894" ], "alert_on_add": true, "auto_add_responding_team": false, "checklists": [], "completed_checks": 0, "external_resources": [], "functionalities": [ { "id": "8861c55d-042d-43f1-9aab-c44c056dcbff", "name": "Platform - Deployments", "slug": "platform-deployments", "description": "", "created_at": "2023-09-05T17:01:48.484Z", "updated_at": "2023-09-05T17:01:48.484Z", "labels": {}, "active_incidents": ["71bdfd6f-4ee7-4222-a9a3-832637174ae7"], "links": [], "owner": "None", "alert_on_add": false, "auto_add_responding_team": "None", "updated_by": { "id": "5ffe7fce-539f-40e7-a6fe-762897190694", "name": "FireHydrant", "source": "patchy", "email": "" }, "services": [ { "id": "87aeea3d-4dcd-4c1e-bf8c-e68e73892e44", "name": "My Test Service", "description": "For testing this service", "slug": "my-test-service", "service_tier": 3, "created_at": "2023-09-06T12:37:37.378Z", "updated_at": "2023-09-20T10:25:32.730Z", "labels": { "myket": "service", "keyval": "app" }, "alert_on_add": true, "auto_add_responding_team": false } ], "external_resources": [], "teams": [] } ], "last_import": "None", "links": [ { "id": "109feace-3aab-415f-9b67-c52418992570", "href_url": "https://example.com", "icon_url": "None", "name": "link text" } ], "managed_by": "None", "managed_by_settings": "None", "owner": { "id": "653c66f8-c14e-4f74-bf9d-9970382f6638", "name": "Test Escalation Team", "description": "This team is for us", "slug": "test-escalation-team", "created_at": "2023-09-06T12:44:16.021Z", "updated_at": "2023-09-06T12:44:16.021Z" }, "service_checklist_updated_at": "None", "teams": [ { "id": "653c66f8-c14e-4f74-bf9d-9970382f6638", "name": "Test Escalation Team", "description": "This team is for us", "slug": "test-escalation-team", "created_at": "2023-09-06T12:44:16.021Z", "updated_at": "2023-09-06T12:44:16.021Z" } ], "updated_by": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "source": "firehydrant_user", "email": "testuser@gmail.com" }, "__incidents": { "milestones": [ [ { "id": "e1c487c2-5479-47e7-96d8-d9a65f3a5288", "created_at": "2023-09-07T21:25:15.876Z", "updated_at": "2023-09-07T21:25:15.876Z", "type": "started", "occurred_at": "2023-09-07T21:25:15.865Z", "duration": "None" }, { "id": "a4b3fab9-3992-49bd-ae01-d277c99e2b97", "created_at": "2023-09-07T21:25:46.535Z", "updated_at": "2023-09-07T21:25:46.543Z", "type": "detected", "occurred_at": "2023-09-07T21:25:46.530Z", "duration": "PT30S" }, { "id": "47920f14-a0c7-4003-a773-dd07eb33d8e5", "created_at": "2023-09-07T21:28:30.792Z", "updated_at": "2023-09-07T21:28:30.805Z", "type": "mitigated", "occurred_at": "2023-09-07T21:28:30.787Z", "duration": "PT2M44S" } ], [ { "id": "293808a2-d214-4f6e-9b46-d055aa5f2540", "created_at": "2023-09-07T21:12:45.129Z", "updated_at": "2023-09-07T21:12:45.129Z", "type": "started", "occurred_at": "2023-09-07T21:12:45.121Z", "duration": "None" }, { "id": "bd073d71-391b-46fd-98b3-62518a9fed8a", "created_at": "2023-09-07T21:12:45.177Z", "updated_at": "2023-09-07T21:12:45.185Z", "type": "acknowledged", "occurred_at": "2023-09-07T21:12:45.121Z", "duration": "PT0S" }, { "id": "4e4db026-710f-4492-913f-9add2ee1b9cb", "created_at": "2023-09-07T21:15:05.564Z", "updated_at": "2023-09-07T21:15:05.576Z", "type": "identified", "occurred_at": "2023-09-07T21:15:05.559Z", "duration": "PT2M20S" }, { "id": "e02eeb8c-2f5c-4f77-a8c0-bda3485e644c", "created_at": "2023-09-07T21:22:39.862Z", "updated_at": "2023-09-07T21:22:39.862Z", "type": "mitigated", "occurred_at": "2023-09-07T21:22:39.817Z", "duration": "PT7M34S" }, { "id": "ee672221-8917-4628-ae8a-ef06e34d02cf", "created_at": "2023-09-07T21:22:39.822Z", "updated_at": "2023-09-07T21:22:39.869Z", "type": "resolved", "occurred_at": "2023-09-07T21:22:39.817Z", "duration": "PT0S" } ], [ { "id": "785599df-be6a-4c13-80ea-11b15eeffa2e", "created_at": "2023-09-05T17:05:25.681Z", "updated_at": "2023-09-05T17:05:25.681Z", "type": "started", "occurred_at": "2023-09-05T17:05:25.671Z", "duration": "None" }, { "id": "a4ee606e-221a-44e2-9c6d-6f83795d0b16", "created_at": "2023-09-07T20:37:07.289Z", "updated_at": "2023-09-07T20:37:07.303Z", "type": "detected", "occurred_at": "2023-09-07T20:37:07.285Z", "duration": "P2DT3H31M41S" }, { "id": "7077e41c-ba30-48c6-9cf1-548c9a692275", "created_at": "2023-09-07T21:13:25.337Z", "updated_at": "2023-09-07T21:13:25.351Z", "type": "identified", "occurred_at": "2023-09-07T21:13:25.333Z", "duration": "PT36M18S" } ] ] } } ``` Port entityJSON ``` { "identifier": "87aeea3d-4dcd-4c1e-bf8c-e68e73892e44", "title": "My Test Service", "icon": "FireHydrant", "blueprint": "firehydrantService", "team": [], "properties": { "description": "For testing this service", "slug": "my-test-service", "links": ["https://example.com"], "labels": { "myket": "service", "keyval": "app" }, "owner": "Test Escalation Team", "createdAt": "2023-09-06T12:37:37.378Z", "updatedAt": "2023-09-20T10:25:32.730Z", "activeIncidents": 2, "meanTimeToAcknowledge": null, "meanTimeToDetect": 25, "meanTimeToMitigate": null, "meanTimeToResolve": null }, "relations": {}, "createdAt": "2023-09-20T10:19:34.745Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-09-20T14:48:30.523Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: incident selector: query: 'true' port: entity: mappings: blueprint: '"firehydrantIncident"' identifier: .id title: .name properties: url: .incident_url priority: .priority severity: .severity tags: .tag_list currentMilestone: .current_milestone functionalities: .functionalities[].name description: .description customerImpact: .customers_impacted commander: .role_assignments[] | select(.incident_role.name == "Commander") | .user.email createdBy: .created_by.email createdAt: .created_at relations: environment: .environments | map(.id) service: .services | map(.id) ``` Sample payloadJSON ``` { "id": "eb03f3f2-1a58-4d19-9f85-b3e46dbf5c2e", "name": "System outage", "created_at": "2023-09-08T10:58:06.791Z", "started_at": "2023-09-08T10:58:07.013Z", "discarded_at": "None", "summary": "", "customer_impact_summary": "", "description": "A system that isn't customer-facing or doesn't impact customer data processing is unavailable", "current_milestone": "acknowledged", "number": 6, "priority": "P2", "severity": "SEV2", "severity_color": "orange", "severity_impact": "None", "severity_condition": "None", "tag_list": ["internal"], "private_id": "4303c837eeb640b39cab772c2f03668e65168041f504eb645027ff362cbac64ad1908268fa802d80205f21e7438516270f607b0f3406564f6d4056985efffdb6e8d2843bb3314426b404a20150cab0a8e9ddcb6114d9cd0140ad9148fb7f7993f827b5e0", "organization_id": "0445b04e-5ed5-4fdc-aef7-5d68046ed78e", "milestones": [ { "id": "64aca0c7-4c92-42f1-9482-7910e46d88bf", "created_at": "2023-09-08T10:58:07.026Z", "updated_at": "2023-09-08T10:58:07.026Z", "type": "started", "occurred_at": "2023-09-08T10:58:07.013Z", "duration": "None" }, { "id": "0778f6c0-999e-47a9-9d53-6cb9df872356", "created_at": "2023-09-08T10:58:07.076Z", "updated_at": "2023-09-08T10:58:07.085Z", "type": "acknowledged", "occurred_at": "2023-09-08T10:58:07.013Z", "duration": "PT0S" } ], "active": true, "labels": {}, "role_assignments": [ { "id": "d8724146-9dcd-4be1-8673-71d48e6192e8", "status": "active", "created_at": "2023-09-08T10:58:08.031Z", "updated_at": "2023-09-08T10:58:08.031Z", "incident_role": { "id": "dace9563-adf1-47a4-889d-a4048b2c07bf", "name": "Commander", "summary": "The incident commander holds the high-level state about the incident", "description": "", "created_at": "2023-09-05T17:01:47.908Z", "updated_at": "2023-09-05T17:01:47.908Z", "discarded_at": "None" }, "user": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "email": "testuser@gmail.com", "slack_user_id": "None", "slack_linked?": false, "created_at": "2023-09-05T17:01:49.368Z", "updated_at": "2023-09-05T17:01:49.368Z" } } ], "status_pages": [], "incident_url": "https://app.firehydrant.io/org/port-5478/incidents/eb03f3f2-1a58-4d19-9f85-b3e46dbf5c2e/incident/overview", "private_status_page_url": "", "organization": { "name": "Port", "id": "0445b04e-5ed5-4fdc-aef7-5d68046ed78e" }, "customers_impacted": 0, "monetary_impact": "None", "monetary_impact_cents": "None", "last_update": "None", "last_note": "None", "report_id": "1d9f12b8-b8ee-458d-a720-7f2429303213", "services": [ { "id": "4e5bb8f3-4eab-4a13-b354-de7fc47f3554", "name": "Port Internal Service Tested" } ], "environments": [], "functionalities": [], "channel_name": "None", "channel_reference": "None", "channel_id": "None", "channel_status": "None", "incident_tickets": [], "ticket": "None", "impacts": [ { "id": "8bbadcd0-9be7-4137-b5f2-9e6df6c26faa", "type": "service", "impact": { "id": "4e5bb8f3-4eab-4a13-b354-de7fc47f3554", "name": "Port Internal Service Tested" }, "condition": { "id": "b1fc9184-b3f3-4af1-a111-68abcad9bf34", "name": "Unavailable", "position": 0 }, "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvOGJiYWRjZDAtOWJlNy00MTM3LWI1ZjItOWU2ZGY2YzI2ZmFh", "resource_class": "Incidents::Impact", "resource_id": "8bbadcd0-9be7-4137-b5f2-9e6df6c26faa", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvOGJiYWRjZDAtOWJlNy00MTM3LWI1ZjItOWU2ZGY2YzI2ZmFh/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvOGJiYWRjZDAtOWJlNy00MTM3LWI1ZjItOWU2ZGY2YzI2ZmFh" } } ] } ], "conference_bridges": [], "incident_channels": [], "retro_exports": [], "created_by": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "source": "firehydrant_user", "email": "testuser@gmail.com" }, "context_object": "None", "restricted": false, "team_assignments": [], "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJl", "resource_class": "Incident", "resource_id": "eb03f3f2-1a58-4d19-9f85-b3e46dbf5c2e", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJl/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJl" } }, { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWRlc2NyaXB0aW9u", "resource_class": "Incident", "resource_id": "eb03f3f2-1a58-4d19-9f85-b3e46dbf5c2e", "field": "description", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWRlc2NyaXB0aW9u/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWRlc2NyaXB0aW9u" } }, { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWN1c3RvbWVyX2ltcGFjdF9zdW1tYXJ5", "resource_class": "Incident", "resource_id": "eb03f3f2-1a58-4d19-9f85-b3e46dbf5c2e", "field": "customer_impact_summary", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWN1c3RvbWVyX2ltcGFjdF9zdW1tYXJ5/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWN1c3RvbWVyX2ltcGFjdF9zdW1tYXJ5" } }, { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWltcGFjdA==", "resource_class": "Incident", "resource_id": "eb03f3f2-1a58-4d19-9f85-b3e46dbf5c2e", "field": "impact", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWltcGFjdA==/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvZWIwM2YzZjItMWE1OC00ZDE5LTlmODUtYjNlNDZkYmY1YzJlP2ZpZWxkPWltcGFjdA==" } } ] } ``` Port entityJSON ``` { "identifier": "eb03f3f2-1a58-4d19-9f85-b3e46dbf5c2e", "title": "System outage", "icon": "FireHydrant", "blueprint": "firehydrantIncident", "team": [], "properties": { "url": "https://app.firehydrant.io/org/port-5478/incidents/eb03f3f2-1a58-4d19-9f85-b3e46dbf5c2e/incident/overview", "priority": "P2", "severity": "SEV2", "tags": ["internal"], "currentMilestone": "acknowledged", "functionalities": null, "customerImpact": "0", "createdBy": "testuser@gmail.com", "createdAt": "2023-09-08T10:58:06.791Z", "description": "A system that isn't customer-facing or doesn't impact customer data processing is unavailable", "commander": "testuser@gmail.com" }, "relations": { "service": ["4e5bb8f3-4eab-4a13-b354-de7fc47f3554"], "environment": [] }, "createdAt": "2023-11-01T11:43:00.904Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-11-01T11:43:00.904Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: retrospective selector: query: .incident.current_milestone == "postmortem_completed" port: entity: mappings: blueprint: '"firehydrantRetrospective"' identifier: .id title: .name properties: url: .incident.incident_url tags: .tag_list services: .incident.services[].name environments: .incident.environments[].name functionalities: .incident.functionalities[].name description: .incident.description customerImpact: .incident.customers_impacted commander: .incident.role_assignments[] | select(.incident_role.name == "Commander") | .user.email createdBy: .incident.created_by.email resolvedAt: .incident.milestones[] | select(.type == "resolved") | .created_at createdAt: .incident.created_at publishedAt: .incident.milestones[] | select(.type == "postmortem_completed") | .created_at duration: (.incident.milestones | map(select(.type == "started" or .type == "resolved")) | sort_by(.occurred_at) | group_by(.type) | map(.[0].occurred_at) | select(length == 2) | ([.[1], .[0]] | map(sub("\\.\\d+Z$"; "Z") | fromdate)) | .[1] - .[0] // null) | ./3600 | floor completedTasks: .__incident.tasks | map(select(.state == "done")) | length incompletedTasks: .__incident.tasks | map(select(.state != "done")) | length questions: '.questions | map({question: .title, answer: .body})' relations: incident: .incident.id ``` Sample payloadJSON ``` { "id": "da2ff7ef-6faf-4e4e-9577-985ffa739c9f", "name": "Bump request library", "summary": "", "incident_id": "81b628c8-8612-4877-803d-22a1dc187ba5", "created_at": "2023-09-05T17:32:52.095Z", "updated_at": "2023-09-05T17:35:20.413Z", "tag_list": [], "additional_details": "None", "incident": { "id": "81b628c8-8612-4877-803d-22a1dc187ba5", "name": "Bump request library", "created_at": "2023-09-05T17:32:51.926Z", "started_at": "2023-09-05T17:32:52.144Z", "discarded_at": "None", "summary": "", "customer_impact_summary": "", "description": "add request library", "current_milestone": "postmortem_completed", "number": 2, "priority": "P1", "severity": "SEV1", "severity_color": "red", "severity_impact": "None", "severity_condition": "None", "tag_list": ["security"], "private_id": "1838d08fe38a7bc92188bc6338918085e521f6cad829101c95f2ba0381da9e26aa7f1bc4247be660735097019bb237351bf451a06d8683310774ca5817661be2b73ad5bf5923d76b79a658667008fc8a8977e9823f2750efebdbbf2fd275f4406e6c870d", "organization_id": "0445b04e-5ed5-4fdc-aef7-5d68046ed78e", "milestones": [ { "id": "9f2e3e86-c306-4b84-a3bb-7b74d6af9747", "created_at": "2023-09-05T17:32:52.161Z", "updated_at": "2023-09-05T17:32:52.161Z", "type": "started", "occurred_at": "2023-09-05T17:32:52.144Z", "duration": "None" }, { "id": "dc8fc26e-3ce4-4a8c-863c-7853367ad81a", "created_at": "2023-09-05T17:32:52.207Z", "updated_at": "2023-09-05T17:32:52.222Z", "type": "acknowledged", "occurred_at": "2023-09-05T17:32:52.144Z", "duration": "PT0S" }, { "id": "d3746e2f-fc65-4a4a-b0db-b784056d7e05", "created_at": "2023-09-05T17:33:28.298Z", "updated_at": "2023-09-05T17:33:28.317Z", "type": "identified", "occurred_at": "2023-09-05T17:33:28.292Z", "duration": "PT36S" }, { "id": "6fd7261d-dceb-40ac-8a3c-7d7fbef4fefe", "created_at": "2023-09-05T17:33:37.592Z", "updated_at": "2023-09-05T17:33:37.592Z", "type": "mitigated", "occurred_at": "2023-09-05T17:33:37.520Z", "duration": "PT9S" }, { "id": "50618295-c422-408d-9880-a077a3c3faa9", "created_at": "2023-09-05T17:33:37.525Z", "updated_at": "2023-09-05T17:33:37.603Z", "type": "resolved", "occurred_at": "2023-09-05T17:33:37.520Z", "duration": "PT0S" }, { "id": "6f95ec3a-165d-4e5e-b3c4-768b28c3d1ee", "created_at": "2023-09-05T17:33:42.693Z", "updated_at": "2023-09-05T17:33:42.717Z", "type": "postmortem_started", "occurred_at": "2023-09-05T17:33:42.689Z", "duration": "PT5S" }, { "id": "3adaac3f-5dd0-4378-b8cd-d33cf9855ae6", "created_at": "2023-09-05T17:36:57.496Z", "updated_at": "2023-09-05T17:36:57.518Z", "type": "postmortem_completed", "occurred_at": "2023-09-05T17:36:57.491Z", "duration": "PT3M14S" } ], "active": false, "labels": {}, "role_assignments": [ { "id": "ea0ff4d9-5efe-4fec-b27f-8eaa82eeb81d", "status": "active", "created_at": "2023-09-05T17:32:52.951Z", "updated_at": "2023-09-05T17:32:52.951Z", "incident_role": { "id": "dace9563-adf1-47a4-889d-a4048b2c07bf", "name": "Commander", "summary": "The incident commander holds the high-level state about the incident", "description": "", "created_at": "2023-09-05T17:01:47.908Z", "updated_at": "2023-09-05T17:01:47.908Z", "discarded_at": "None" }, "user": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "email": "testuser@gmail.com", "slack_user_id": "None", "slack_linked?": false, "created_at": "2023-09-05T17:01:49.368Z", "updated_at": "2023-09-05T17:01:49.368Z" } } ], "status_pages": [], "incident_url": "https://app.firehydrant.io/org/port-5478/incidents/81b628c8-8612-4877-803d-22a1dc187ba5/incident/overview", "private_status_page_url": "", "organization": { "name": "Port", "id": "0445b04e-5ed5-4fdc-aef7-5d68046ed78e" }, "customers_impacted": 0, "monetary_impact": "None", "monetary_impact_cents": "None", "last_update": "None", "last_note": "None", "report_id": "da2ff7ef-6faf-4e4e-9577-985ffa739c9f", "services": [], "environments": [ { "id": "21011924-215e-4aa4-abbf-6155ecf237ae", "name": "Production" } ], "functionalities": [ { "id": "8861c55d-042d-43f1-9aab-c44c056dcbff", "name": "Platform - Deployments" } ], "channel_name": "None", "channel_reference": "None", "channel_id": "None", "channel_status": "None", "incident_tickets": [], "ticket": "None", "impacts": [ { "id": "749de40b-9368-4145-aa75-2d7177d60828", "type": "environment", "impact": { "id": "21011924-215e-4aa4-abbf-6155ecf237ae", "name": "Production" }, "condition": { "id": "b1fc9184-b3f3-4af1-a111-68abcad9bf34", "name": "Unavailable", "position": 0 }, "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvNzQ5ZGU0MGItOTM2OC00MTQ1LWFhNzUtMmQ3MTc3ZDYwODI4", "resource_class": "Incidents::Impact", "resource_id": "749de40b-9368-4145-aa75-2d7177d60828", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvNzQ5ZGU0MGItOTM2OC00MTQ1LWFhNzUtMmQ3MTc3ZDYwODI4/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvNzQ5ZGU0MGItOTM2OC00MTQ1LWFhNzUtMmQ3MTc3ZDYwODI4" } } ] }, { "id": "b6833302-01f8-4705-b863-178e35cd7b23", "type": "functionality", "impact": { "id": "8861c55d-042d-43f1-9aab-c44c056dcbff", "name": "Platform - Deployments" }, "condition": { "id": "c2bc11e8-7fcc-428d-9007-26f00700834a", "name": "Bug", "position": 2 }, "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvYjY4MzMzMDItMDFmOC00NzA1LWI4NjMtMTc4ZTM1Y2Q3YjIz", "resource_class": "Incidents::Impact", "resource_id": "b6833302-01f8-4705-b863-178e35cd7b23", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvYjY4MzMzMDItMDFmOC00NzA1LWI4NjMtMTc4ZTM1Y2Q3YjIz/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnRzOjpJbXBhY3QvYjY4MzMzMDItMDFmOC00NzA1LWI4NjMtMTc4ZTM1Y2Q3YjIz" } } ] } ], "conference_bridges": [], "incident_channels": [], "retro_exports": [], "created_by": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "source": "firehydrant_user", "email": "testuser@gmail.com" }, "context_object": "None", "restricted": false, "team_assignments": [], "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1", "resource_class": "Incident", "resource_id": "81b628c8-8612-4877-803d-22a1dc187ba5", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1" } }, { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWRlc2NyaXB0aW9u", "resource_class": "Incident", "resource_id": "81b628c8-8612-4877-803d-22a1dc187ba5", "field": "description", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWRlc2NyaXB0aW9u/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWRlc2NyaXB0aW9u" } }, { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWN1c3RvbWVyX2ltcGFjdF9zdW1tYXJ5", "resource_class": "Incident", "resource_id": "81b628c8-8612-4877-803d-22a1dc187ba5", "field": "customer_impact_summary", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWN1c3RvbWVyX2ltcGFjdF9zdW1tYXJ5/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWN1c3RvbWVyX2ltcGFjdF9zdW1tYXJ5" } }, { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWltcGFjdA==", "resource_class": "Incident", "resource_id": "81b628c8-8612-4877-803d-22a1dc187ba5", "field": "impact", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWltcGFjdA==/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svSW5jaWRlbnQvODFiNjI4YzgtODYxMi00ODc3LTgwM2QtMjJhMWRjMTg3YmE1P2ZpZWxkPWltcGFjdA==" } } ] }, "questions": [ { "id": "0ee80dda-a6b0-418d-aca2-8d12cfe58523", "title": "What went well?", "body": "able to resolve it fast", "tooltip": "What process went well during your incident? What mitigation steps went well?", "kind": "freeform", "is_required": true, "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzBlZTgwZGRhLWE2YjAtNDE4ZC1hY2EyLThkMTJjZmU1ODUyMw==", "resource_class": "PostMortems::Question", "resource_id": "0ee80dda-a6b0-418d-aca2-8d12cfe58523", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzBlZTgwZGRhLWE2YjAtNDE4ZC1hY2EyLThkMTJjZmU1ODUyMw==/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzBlZTgwZGRhLWE2YjAtNDE4ZC1hY2EyLThkMTJjZmU1ODUyMw==" } } ] }, { "id": "7fbe151b-6040-49ab-9c5b-348866cabad6", "title": "What could be improved?", "body": "nothing", "tooltip": "What parts of your incident response process can you improve? What additional things could you put in place to ensure that an issue like this can be mitigated in the future?", "kind": "freeform", "is_required": true, "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzdmYmUxNTFiLTYwNDAtNDlhYi05YzViLTM0ODg2NmNhYmFkNg==", "resource_class": "PostMortems::Question", "resource_id": "7fbe151b-6040-49ab-9c5b-348866cabad6", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzdmYmUxNTFiLTYwNDAtNDlhYi05YzViLTM0ODg2NmNhYmFkNg==/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzdmYmUxNTFiLTYwNDAtNDlhYi05YzViLTM0ODg2NmNhYmFkNg==" } } ] }, { "id": "d77a6858-6763-4e84-8aec-b37f924c2428", "title": "Where did we get lucky?", "body": "everything", "tooltip": "What near misses did you have during your incident?", "kind": "freeform", "is_required": true, "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uL2Q3N2E2ODU4LTY3NjMtNGU4NC04YWVjLWIzN2Y5MjRjMjQyOA==", "resource_class": "PostMortems::Question", "resource_id": "d77a6858-6763-4e84-8aec-b37f924c2428", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uL2Q3N2E2ODU4LTY3NjMtNGU4NC04YWVjLWIzN2Y5MjRjMjQyOA==/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uL2Q3N2E2ODU4LTY3NjMtNGU4NC04YWVjLWIzN2Y5MjRjMjQyOA==" } } ] }, { "id": "4558de04-11f2-4a06-9253-4970d25610b0", "title": "What were we wrong about?", "body": "none", "tooltip": "What ideas did you have during your mitigation that were incorrect?", "kind": "freeform", "is_required": false, "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzQ1NThkZTA0LTExZjItNGEwNi05MjUzLTQ5NzBkMjU2MTBiMA==", "resource_class": "PostMortems::Question", "resource_id": "4558de04-11f2-4a06-9253-4970d25610b0", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzQ1NThkZTA0LTExZjItNGEwNi05MjUzLTQ5NzBkMjU2MTBiMA==/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzQ1NThkZTA0LTExZjItNGEwNi05MjUzLTQ5NzBkMjU2MTBiMA==" } } ] }, { "id": "4dc6dc62-b366-4028-84c7-367dc09c2496", "title": "Additional Details", "body": "None", "tooltip": "", "kind": "freeform", "is_required": false, "conversations": [ { "id": "Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzRkYzZkYzYyLWIzNjYtNDAyOC04NGM3LTM2N2RjMDljMjQ5Ng==", "resource_class": "PostMortems::Question", "resource_id": "4dc6dc62-b366-4028-84c7-367dc09c2496", "field": "None", "comments_url": "/v1/conversations/Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzRkYzZkYzYyLWIzNjYtNDAyOC04NGM3LTM2N2RjMDljMjQ5Ng==/comments", "channel": { "name": "private-conversations-Z2lkOi8vbGFkZGVydHJ1Y2svUG9zdE1vcnRlbXM6OlF1ZXN0aW9uLzRkYzZkYzYyLWIzNjYtNDAyOC04NGM3LTM2N2RjMDljMjQ5Ng==" } } ] } ], "__incident": { "tasks": [ { "id": "e56c4194-0070-4d8d-bc75-9df60f699c90", "title": "Update Incident Details", "description": "Determine the severity and impact of the incident and update the details", "state": "open", "assignee": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "source": "firehydrant_user", "email": "testuser@gmail.com" }, "created_by": { "id": "5ffe7fce-539f-40e7-a6fe-762897190694", "name": "FireHydrant", "source": "patchy", "email": "" }, "created_at": "2023-09-05T17:32:53.269Z", "updated_at": "2023-09-05T17:32:53.300Z" }, { "id": "ee23f50b-a30a-4005-b2cd-74a711cf8c88", "title": "Assign Teams and/or Roles", "description": "Assign any teams or specific roles for the incident", "state": "done", "assignee": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "source": "firehydrant_user", "email": "testuser@gmail.com" }, "created_by": { "id": "5ffe7fce-539f-40e7-a6fe-762897190694", "name": "FireHydrant", "source": "patchy", "email": "" }, "created_at": "2023-09-05T17:32:53.320Z", "updated_at": "2023-09-05T18:11:16.371Z" }, { "id": "5ed1c84f-187a-4899-84a9-361d7a32f62a", "title": "Status Update", "description": "Add an update to your status page", "state": "done", "assignee": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "source": "firehydrant_user", "email": "testuser@gmail.com" }, "created_by": { "id": "5ffe7fce-539f-40e7-a6fe-762897190694", "name": "FireHydrant", "source": "patchy", "email": "" }, "created_at": "2023-09-05T17:32:53.366Z", "updated_at": "2023-09-05T17:34:30.190Z" }, { "id": "09520e6b-d6d4-42a0-aaa0-0596b0d8a424", "title": "Resolve Incident", "description": "Star important timeline items and resolve incident", "state": "open", "assignee": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "source": "firehydrant_user", "email": "testuser@gmail.com" }, "created_by": { "id": "5ffe7fce-539f-40e7-a6fe-762897190694", "name": "FireHydrant", "source": "patchy", "email": "" }, "created_at": "2023-09-05T17:32:53.410Z", "updated_at": "2023-09-05T17:32:53.439Z" }, { "id": "73a6a22e-5464-423a-99f1-e497a81a630a", "title": "Schedule Retro", "description": "Schedule a retrospective with the responding team", "state": "open", "assignee": { "id": "184eb568-c1a9-4f99-9d52-8f7dd6672158", "name": "My Username", "source": "firehydrant_user", "email": "testuser@gmail.com" }, "created_by": { "id": "5ffe7fce-539f-40e7-a6fe-762897190694", "name": "FireHydrant", "source": "patchy", "email": "" }, "created_at": "2023-09-05T17:32:53.454Z", "updated_at": "2023-09-05T17:32:53.482Z" } ] } } ``` Port entityJSON ``` { "identifier": "da2ff7ef-6faf-4e4e-9577-985ffa739c9f", "title": "Bump request library", "icon": "FireHydrant", "blueprint": "firehydrantRetrospective", "team": [], "properties": { "url": "https://app.firehydrant.io/org/port-5478/incidents/81b628c8-8612-4877-803d-22a1dc187ba5/incident/overview", "tags": [], "services": null, "environments": ["Production"], "functionalities": ["Platform - Deployments"], "createdBy": "testuser@gmail.com", "createdAt": "2023-09-05T17:32:51.926Z", "customerImpact": "0", "commander": "testuser@gmail.com", "resolvedAt": "2023-09-05T17:33:37.525Z", "publishedAt": "2023-09-05T17:36:57.496Z", "duration": null, "completedTasks": null, "incompletedTasks": null, "questions": [ { "question": "What went well?", "answer": "able to resolve it fast" }, { "question": "What could be improved?", "answer": "nothing" }, { "question": "Where did we get lucky?", "answer": "everything" }, { "question": "What were we wrong about?", "answer": "none" }, { "question": "Additional Details" } ] }, "relations": { "incident": "81b628c8-8612-4877-803d-22a1dc187ba5" }, "createdAt": "2023-11-01T11:43:05.551Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-11-01T11:43:05.551Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # Incident.io Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's Incident.io integration allows you to model Incident.io resources in your software catalog and ingest data into them using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The Incident.io integration can ingest the following resources into Port: * `user` - Members of your Incident.io organization from [`/v2/users`](https://api-docs.incident.io/#tag/Users-V2). * `alert-source` - Configured alert sources from [`/v2/alert_sources`](https://api-docs.incident.io/#tag/Alert-Sources-V2). * `schedule` - On-call schedules and current shifts from [`/v2/schedules`](https://api-docs.incident.io/#tag/Schedules-V2). * `alert` - Fired and resolved alerts from [`/v2/alerts`](https://api-docs.incident.io/#tag/Alerts-V2). * `incident` - Active and historical incidents from [`/v2/incidents`](https://api-docs.incident.io/#tag/Incidents-V2). * `catalog-type` - Catalog type definitions from [`/v3/catalog_types`](https://api-docs.incident.io/#tag/Catalog-Types-V3). * `catalog-entry` - Entries under each catalog type from [`/v3/catalog_types/{catalog_type_id}/entries`](https://api-docs.incident.io/#tag/Catalog-Entries-V3). * `severity` - Incident severities from [`/v1/severities`](https://api-docs.incident.io/#tag/Severities-V1). * `team` - Response and ownership teams from [`/v3/teams`](https://api-docs.incident.io/#tag/Teams-V3). * `follow-up` - Follow-up actions linked to incidents from [`/v2/follow_ups`](https://api-docs.incident.io/#tag/Follow-Ups-V2). It is possible to reference any field that appears in the API responses linked above in the mapping configuration. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, you need: * An Incident.io API key with read access to your organization. **To generate an Incident.io API key:** 1. Log in to your [Incident.io dashboard](https://app.incident.io). 2. Navigate to **Settings** and select **API keys**. 3. Click **Create API key** and give it a descriptive name (e.g., "Port Integration"). 4. Select **Read-only** access for all required resources. 5. Copy and store the generated API key securely - it will only be shown once. Keep your API key secure Never commit your API key to source control or share it publicly. Store it as a secret in your deployment environment. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. In the configuration form, enter the following values: * **Identifier**: `my-ocean-incidentio-integration`. * **Base URL**: `https://api.incident.io`. * **Authentication type**: `bearer_token`. * **Token**: your Incident.io API key. 3. Expand **Advanced configuration** and set: * **Pagination type**: `cursor`. * **Pagination parameter**: `after`. * **Cursor path**: `pagination_meta.after`. * **Has more path**: `pagination_meta.after`. * **Page size**: `100`. * **Page size parameter**: `page_size`. 4. Click **Save & install**. Continue with the mappings After the integration is installed, continue to the **Configuration** section on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation 1. Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_INCIDENT_IO_API_KEY`. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-incidentio-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set scheduledResyncInterval=60 \ --set integration.identifier="my-ocean-incidentio-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://api.incident.io" \ --set integration.config.authType="bearer_token" \ --set integration.secrets.token="YOUR_INCIDENT_IO_API_KEY" \ --set integration.config.paginationType="cursor" \ --set integration.config.paginationParam="after" \ --set integration.config.cursorPath="pagination_meta.after" \ --set integration.config.hasMorePath="pagination_meta.after" \ --set integration.config.pageSize=100 \ --set integration.config.sizeParam="page_size" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters This table summarizes the available parameters for the installation. | Parameter | Description | Example | Required | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- | -------- | | `port.clientId` | Your Port [client ID](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.baseUrl` | Your Port API URL - `https://api.port.io` for EU, `https://api.us.port.io` for US | | ✅ | | `integration.config.baseUrl` | The base URL of the Incident.io API | `https://api.incident.io` | ✅ | | `integration.config.authType` | The authentication type (use `bearer_token` for Incident.io) | `bearer_token` | ✅ | | `integration.secrets.token` | Your Incident.io API key | | ✅ | | `integration.config.paginationType` | Pagination method used by the Incident.io API | `cursor` | ✅ | | `integration.config.paginationParam` | The query parameter name for the cursor | `after` | ✅ | | `integration.config.cursorPath` | Dot-notation path to the cursor value in the response | `pagination_meta.after` | ✅ | | `integration.config.hasMorePath` | Dot-notation path to check if more pages exist | `pagination_meta.after` | ✅ | | `integration.config.pageSize` | Number of items to fetch per page | `100` | ✅ | | `integration.config.sizeParam` | The query parameter name for the page size | `page_size` | ✅ | | `integration.eventListener.type` | The event listener type. Read more about [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) | `POLLING` | ✅ | | `integration.type` | The integration type (must be `custom` for the no-code Ocean custom integration). | `custom` | ✅ | | `integration.identifier` | Unique identifier for the integration instance | `my-ocean-incidentio-integration` | ✅ | | `scheduledResyncInterval` | The number of minutes between each resync. Read more about [scheduledResyncInterval](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) | `60` | ❌ | | `initializePortResources` | When set to `true`, the integration will create default blueprints and the Port app config mapping | `true` | ❌ | | `sendRawDataExamples` | Enable sending raw data examples from the Incident.io API to Port for testing and managing the integration mapping. Default is `true` | `true` | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_INCIDENT_IO_API_KEY`. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://api.incident.io" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__SECRETS__TOKEN="YOUR_INCIDENT_IO_API_KEY" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="cursor" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_PARAM="after" \ -e OCEAN__INTEGRATION__CONFIG__CURSOR_PATH="pagination_meta.after" \ -e OCEAN__INTEGRATION__CONFIG__HAS_MORE_PATH="pagination_meta.after" \ -e OCEAN__INTEGRATION__CONFIG__PAGE_SIZE="100" \ -e OCEAN__INTEGRATION__CONFIG__SIZE_PARAM="page_size" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before the integration can sync data, you need to create the required blueprints in Port. These blueprints define the data model for your Incident.io resources. **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on `+ Blueprint`. 3. Click on the `{...} Edit JSON` button. 4. Copy and paste the following JSON configuration: If you plan to sync `catalog_types`, `catalog_entries`, `severities`, `teams`, or `follow_ups`, create their matching blueprints and relations before adding the corresponding mappings in the **Configuration** section. Create blueprints in order We recommend creating blueprints in the order they appear below. Blueprints with no relations (User and Alert Source) should be created first, since other blueprints reference them. **User blueprint (click to expand)** Members of your Incident.io organization: Create in Port ``` { "identifier": "incidentioUser", "title": "Incident.io User", "icon": "User", "description": "A member of your Incident.io organization", "schema": { "properties": { "email": { "type": "string", "format": "email", "title": "Email" }, "role": { "type": "string", "title": "Role" }, "slackUserId": { "type": "string", "title": "Slack User ID" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Alert source blueprint (click to expand)** Configured alert sources in your Incident.io organization: Create in Port ``` { "identifier": "incidentioAlertSource", "title": "Incident.io Alert Source", "icon": "Webhook", "description": "A configured alert source in Incident.io", "schema": { "properties": { "sourceType": { "type": "string", "title": "Source Type" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Schedule blueprint (click to expand)** On-call schedules with a relation to the users currently on shift: Create in Port ``` { "identifier": "incidentioSchedule", "title": "Incident.io Schedule", "icon": "Calendar", "description": "An on-call schedule in Incident.io", "schema": { "properties": { "timezone": { "type": "string", "title": "Timezone" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "currentOnCall": { "title": "Current On-Call", "target": "incidentioUser", "required": false, "many": true } } } ``` **Alert blueprint (click to expand)** Fired and resolved alerts with a relation to their source: Create in Port ``` { "identifier": "incidentioAlert", "title": "Incident.io Alert", "icon": "Alert", "description": "A fired or resolved alert in Incident.io", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "status": { "type": "string", "title": "Status", "enum": ["firing", "resolved"], "enumColors": { "firing": "red", "resolved": "green" } }, "priority": { "type": "string", "title": "Priority" }, "sourceUrl": { "type": "string", "format": "url", "title": "Source URL" }, "deduplicationKey": { "type": "string", "title": "Deduplication Key" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "alertSource": { "title": "Alert Source", "target": "incidentioAlertSource", "required": false, "many": false } } } ``` **Incident blueprint (click to expand)** Active and historical incidents with relations to the incident commander: Create in Port ``` { "identifier": "incidentioIncident", "title": "Incident.io Incident", "icon": "Incident", "description": "An active or historical incident in Incident.io", "schema": { "properties": { "status": { "type": "string", "title": "Status", "enum": [ "triage", "investigating", "fixing", "monitoring", "resolved", "declined" ], "enumColors": { "triage": "orange", "investigating": "yellow", "fixing": "blue", "monitoring": "purple", "resolved": "green", "declined": "lightGray" } }, "severity": { "type": "string", "title": "Severity" }, "summary": { "type": "string", "title": "Summary" }, "permalink": { "type": "string", "format": "url", "title": "Permalink" }, "resolvedAt": { "type": "string", "format": "date-time", "title": "Resolved At" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Updated At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "commander": { "title": "Commander", "target": "incidentioUser", "required": false, "many": false } } } ``` **Catalog type blueprint (click to expand)** Catalog type definitions used to classify entities in Incident.io: Create in Port ``` { "identifier": "incidentioCatalogType", "title": "Incident.io Catalog Type", "icon": "Catalog", "description": "A catalog type definition in Incident.io", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "typeName": { "type": "string", "title": "Type Name" }, "categories": { "type": "array", "title": "Categories", "items": { "type": "string" } }, "sourceRepoUrl": { "type": "string", "format": "url", "title": "Source Repo URL" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Catalog entry blueprint (click to expand)** Catalog entries with a relation to their parent catalog type: Create in Port ``` { "identifier": "incidentioCatalogEntry", "title": "Incident.io Catalog Entry", "icon": "DefaultProperty", "description": "A catalog entry in Incident.io", "schema": { "properties": { "externalId": { "type": "string", "title": "External ID" }, "attributeValues": { "type": "object", "title": "Attribute Values" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "catalogType": { "title": "Catalog Type", "target": "incidentioCatalogType", "required": false, "many": false } } } ``` **Severity blueprint (click to expand)** Severity levels used by incidents: Create in Port ``` { "identifier": "incidentioSeverity", "title": "Incident.io Severity", "icon": "Alert", "description": "A severity level in Incident.io", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "rank": { "type": "number", "title": "Rank" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Team blueprint (click to expand)** Teams in your Incident.io workspace: Create in Port ``` { "identifier": "incidentioTeam", "title": "Incident.io Team", "icon": "Team", "description": "A team in Incident.io", "schema": { "properties": { "slackChannelId": { "type": "string", "title": "Slack Channel ID" }, "memberCount": { "type": "number", "title": "Member Count" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Follow-up blueprint (click to expand)** Follow-up actions with a relation to the linked incident: Create in Port ``` { "identifier": "incidentioFollowUp", "title": "Incident.io Follow-up", "icon": "Checklist", "description": "A follow-up action in Incident.io", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "status": { "type": "string", "title": "Status" }, "priority": { "type": "string", "title": "Priority" }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "incident": { "title": "Incident", "target": "incidentioIncident", "required": false, "many": false } } } ``` 5. Click **Save** after creating each blueprint. ## Configuration[​](#configuration "Direct link to Configuration") After installation, define which endpoints to sync in your integration configuration. Each resource maps an API endpoint to Port entities using [JQ expressions](https://stedolan.github.io/jq/manual/) to transform the data. **Key mapping components:** * **`kind`**: The API endpoint path, combined with your base URL to form the full request URL. * **`selector.query`**: JQ filter to include or exclude entities. Use `'true'` to sync all. * **`selector.data_path`**: JQ expression pointing to the array of items in the response. * **`port.entity.mappings`**: How to map API fields to Port entity properties and relations. For more details on how the no-code Ocean custom integration works, see the [How it works](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview#how-it-works) section. Incident.io API reference The full Incident.io API reference is available at [api-docs.incident.io](https://api-docs.incident.io/). Each mapping section below links directly to the relevant endpoint documentation. **Incident.io API response format:** All Incident.io API responses follow this structure: ``` { "resource_name": [...], "pagination_meta": { "page_size": 100, "after": "CURSOR_VALUE" } } ``` The data array is always nested under a resource-specific key (e.g., `alerts`, `incidents`, `users`). The `pagination_meta.after` field contains the cursor for the next page and is absent when there are no more pages. Sync order matters We recommend adding mappings in the order shown below. Sync `catalog_types`, `severities`, `users`, and `alert_sources` first, then sync dependent resources such as `catalog_entries`, `schedules`, `teams`, `alerts`, `incidents`, and `follow_ups`. **To configure the mappings:** 1. Go to your [connectors page](https://app.port.io/settings/data-sources). 2. Find your Incident.io integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below. **Users mapping (click to expand)** **API reference:** \[`GET /v2/users`]\( V2\_List) ``` resources: - kind: /v2/users selector: query: "true" data_path: ".users" port: entity: mappings: identifier: .id title: .name blueprint: '"incidentioUser"' properties: email: .email role: .role slackUserId: .slack_user_id createdAt: .created_at ``` **Alert sources mapping (click to expand)** **API reference:** [`GET /v2/alert_sources`](https://api-docs.incident.io/#tag/Alert-Sources-V2/operation/Alert%20Sources%20V2_List) ``` resources: - kind: /v2/alert_sources selector: query: "true" data_path: ".alert_sources" port: entity: mappings: identifier: .id title: .name blueprint: '"incidentioAlertSource"' properties: sourceType: .source_type createdAt: .created_at updatedAt: .updated_at ``` **Schedules mapping (click to expand)** **API reference:** [`GET /v2/schedules`](https://api-docs.incident.io/#tag/Schedules-V2/operation/Schedules%20V2_List) ``` resources: - kind: /v2/schedules selector: query: "true" data_path: ".schedules" port: entity: mappings: identifier: .id title: .name blueprint: '"incidentioSchedule"' properties: timezone: .timezone createdAt: .created_at updatedAt: .updated_at relations: currentOnCall: >- .current_shifts // [] | map(.user.id) ``` **Alerts mapping (click to expand)** **API reference:** [`GET /v2/alerts`](https://api-docs.incident.io/#tag/Alerts-V2/operation/Alerts%20V2_List) ``` resources: - kind: /v2/alerts selector: query: "true" data_path: ".alerts" port: entity: mappings: identifier: .id title: .title blueprint: '"incidentioAlert"' properties: description: .description status: .status priority: >- (.attributes // [] | map(select(.attribute.name == "Priority")) | first | .value.label) // null sourceUrl: .source_url deduplicationKey: .deduplication_key createdAt: .created_at updatedAt: .updated_at relations: alertSource: .alert_source_id ``` **Incidents mapping (click to expand)** **API reference:** [`GET /v2/incidents`](https://api-docs.incident.io/#tag/Incidents-V2/operation/Incidents%20V2_List) ``` resources: - kind: /v2/incidents selector: query: "true" data_path: ".incidents" port: entity: mappings: identifier: .id title: .name blueprint: '"incidentioIncident"' properties: status: .status severity: .severity.name summary: .summary permalink: .permalink resolvedAt: .resolved_at createdAt: .created_at updatedAt: .updated_at relations: commander: >- .incident_role_assignments // [] | map(select(.role.role_type == "commander")) | first | .assignee.id // null ``` **Catalog types mapping (click to expand)** **API reference:** [`GET /v3/catalog_types`](https://api-docs.incident.io/#tag/Catalog-Types-V3) ``` resources: - kind: /v3/catalog_types selector: query: "true" data_path: ".catalog_types" port: entity: mappings: identifier: .id title: .name blueprint: '"incidentioCatalogType"' properties: description: .description typeName: .type_name categories: .categories sourceRepoUrl: .source_repo_url createdAt: .created_at ``` **Catalog entries mapping (click to expand)** **API reference:** [`GET /v3/catalog_types/{catalog_type_id}/entries`](https://api-docs.incident.io/#tag/Catalog-Entries-V3) ``` resources: - kind: /v3/catalog_types/{catalog_type_id}/entries selector: query: "true" data_path: ".catalog_entries" path_parameters: catalog_type_id: endpoint: /v3/catalog_types field: .id filter: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"incidentioCatalogEntry"' properties: externalId: .external_id attributeValues: .attribute_values createdAt: .created_at relations: catalogType: .__catalog_type_id ``` **Severities mapping (click to expand)** **API reference:** [`GET /v1/severities`](https://api-docs.incident.io/#tag/Severities-V1) ``` resources: - kind: /v1/severities selector: query: "true" data_path: ".severities" port: entity: mappings: identifier: .id title: .name blueprint: '"incidentioSeverity"' properties: description: .description rank: .rank createdAt: .created_at ``` **Teams mapping (click to expand)** **API reference:** [`GET /v3/teams`](https://api-docs.incident.io/#tag/Teams-V3) ``` resources: - kind: /v3/teams selector: query: "true" data_path: ".teams" port: entity: mappings: identifier: .id title: .name blueprint: '"incidentioTeam"' properties: slackChannelId: .slack_channel_id memberCount: "(.members // [] | length)" createdAt: .created_at ``` **Follow-ups mapping (click to expand)** **API reference:** [`GET /v2/follow_ups`](https://api-docs.incident.io/#tag/Follow-Ups-V2) ``` resources: - kind: /v2/follow_ups selector: query: "true" data_path: ".follow_ups" port: entity: mappings: identifier: .id title: .title blueprint: '"incidentioFollowUp"' properties: description: .description status: .status priority: .priority.name createdAt: .created_at relations: incident: .incident_id ``` 5. Click **Save** to apply the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to test different API endpoints or explore the Incident.io API before committing to a configuration, use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md). **The interactive builder helps you:** 1. Test your Incident.io API endpoints with live data. 2. Automatically detect the data structure and field types. 3. Generate blueprints and resource mappings tailored to your preferences. 4. Get installation commands with your configuration pre-filled. Simply provide your Incident.io API details, and the builder will generate everything you need. ## Additional resources[​](#additional-resources "Direct link to Additional resources") * [Incident.io API reference](https://api-docs.incident.io/) - full reference for all available endpoints and response schemas. * [Incident.io API authentication](https://api-docs.incident.io/#section/Making-a-request) - details on how to authenticate requests to the Incident.io API. * [No-code Ocean custom integration overview](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) - learn how the no-code Ocean custom integration framework works. * [No-code Ocean custom integration configuration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/configuration/.md) - advanced mapping options, nested endpoints, and pagination configuration. --- # Jira Service Management (JSM) Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's Jira Service Management integration allows you to model JSM service desk resources in your software catalog and ingest data using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. This guide focuses on three [Service Desk REST API](https://developer.atlassian.com/cloud/jira/service-desk/rest/intro/) endpoints: listing service desks, customer requests, and organizations. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The integration can ingest the following resources: * `service-desk`: service desks from [`GET /rest/servicedeskapi/servicedesk`](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-get). * `customer-request`: customer requests from [`GET /rest/servicedeskapi/request`](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-get). * `organization`: organizations from [`GET /rest/servicedeskapi/organization`](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-get). You can reference any fields returned by these endpoints in the mapping configuration. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") You need: * **Jira Service Management Cloud** on Atlassian Cloud. Your site URL follows the form `https://your-domain.atlassian.net`. * An **[Atlassian API token](https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/)** for `basic` authentication (recommended for integrations). * An Atlassian user that can read service desks, customer requests, and organizations in JSM. **To create an API token:** 1. Log in at [Atlassian account security - API tokens](https://id.atlassian.com/manage-profile/security/api-tokens). 2. Choose whether you want a scoped token or an all-purpose token. Either works with this integration when you set **base URL** the way Atlassian expects for that token type. * **Create API token with scopes** if you want a restricted token and you select only the scopes for the actions you need. Choose read scopes that cover the Service Desk endpoints you sync. Atlassian lists the permission names on each operation in the [Service Desk REST API reference](https://developer.atlassian.com/cloud/jira/service-desk/rest/intro/) (they appear as OAuth scopes in the docs). Use Atlassian's gateway **base URL**: `https://api.atlassian.com/ex/jira/{cloudId}`. Find your Cloud ID in [Retrieve my Atlassian sites Cloud ID](https://support.atlassian.com/jira/kb/retrieve-my-atlassian-sites-cloud-id/). * **Create API token** if you want an all-purpose token with the same broad access as your account (no per-action scope picker). Use your Jira site **base URL**: `https://YOUR_DOMAIN.atlassian.net`, as in the **Installation** examples on this page. 3. Name the token (for example **Port Ocean**), click **Create**, then copy it and store it securely. You cannot retrieve it later. **Basic authentication for Jira Cloud:** use your Atlassian account **email address** as the username and the **API token** as the password in the integration secrets (see the **Installation** section on this page). Keep credentials secure Never commit credentials to source control. Store them only in secrets for your Ocean deployment or in Port-hosted integration settings. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. Configure: * **Identifier**: `my-ocean-jsm-integration`. * **Base URL**: `https://YOUR_DOMAIN.atlassian.net` (your Jira Cloud site). * **Authentication type**: `basic`. * **Username**: your Atlassian account email address. * **Password**: your Jira API token. 3. Expand **Advanced configuration** and set: * **Pagination type**: `next_link`. * **Next link path**: `_links.next`. * **Page size**: `50` or `100` (maps to Atlassian `limit` on first requests). 4. Click **Save & install**. Continue with the mappings After the integration is installed, continue to **Configuration** on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation 1. Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Replace placeholders for Port credentials (`YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`), your **`YOUR_ATLASSIAN_EMAIL`**, **`YOUR_JIRA_API_TOKEN`**, and your site URL **`https://YOUR_DOMAIN.atlassian.net`**. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-jsm-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set scheduledResyncInterval=60 \ --set integration.identifier="my-ocean-jsm-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://YOUR_DOMAIN.atlassian.net" \ --set integration.config.authType="basic" \ --set integration.secrets.username="YOUR_ATLASSIAN_EMAIL" \ --set integration.secrets.password="YOUR_JIRA_API_TOKEN" \ --set integration.config.paginationType="link" \ --set integration.config.nextLinkPath="_links.next" \ --set integration.config.pageSize=50 ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters | Parameter | Description | Example | Required | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -------- | | `port.clientId` | Your Port [client ID](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.baseUrl` | Your Port API URL (`https://api.port.io` for EU, `https://api.us.port.io` for US) | | ✅ | | `integration.config.baseUrl` | Jira Cloud site root (no `/rest/...` suffix) | `https://your-domain.atlassian.net` | ✅ | | `integration.config.authType` | Use `basic` with email + API token | `basic` | ✅ | | `integration.secrets.username` | Atlassian account email | `you@company.com` | ✅ | | `integration.secrets.password` | Jira Cloud API token | | ✅ | | `integration.config.paginationType` | JSM returns next page URLs in JSON; use `link` | `link` | ✅ | | `integration.config.nextLinkPath` | Path to the next page URL in each response | `_links.next` | ✅ | | `integration.config.pageSize` | Page size for the first request (`limit` query param) | `50` | ❌ | | `integration.eventListener.type` | Event listener type | `POLLING` | ✅ | | `integration.type` | Must be `custom` for the no-code Ocean custom integration | `custom` | ✅ | | `integration.identifier` | Unique integration instance ID | `my-ocean-jsm-integration` | ✅ | | `scheduledResyncInterval` | Minutes between resyncs | `60` | ❌ | | `initializePortResources` | Create default blueprints/mapping on first run | `true` | ❌ | | `sendRawDataExamples` | Send raw API examples to Port for mapping tests | `true` | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Replace Port credentials, `YOUR_ATLASSIAN_EMAIL`, `YOUR_JIRA_API_TOKEN`, and `YOUR_DOMAIN`. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://YOUR_DOMAIN.atlassian.net" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="basic" \ -e OCEAN__INTEGRATION__SECRETS__USERNAME="YOUR_ATLASSIAN_EMAIL" \ -e OCEAN__INTEGRATION__SECRETS__PASSWORD="YOUR_JIRA_API_TOKEN" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="link" \ -e OCEAN__INTEGRATION__CONFIG__NEXT_LINK_PATH="_links.next" \ -e OCEAN__INTEGRATION__CONFIG__PAGE_SIZE="50" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Create blueprints before syncing. Create **`jsmServiceDesk`** before **`jsmCustomerRequest`**, because the customer request blueprint relates requests to a service desk. **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on `+ Blueprint`. 3. Click on the `{...} Edit JSON` button. 4. Copy and paste the following JSON configuration: Recommended order: service desk, then customer request, then organization. Create the service desk blueprint before the customer request blueprint because of the relation. **JSM Service desk blueprint (click to expand)** Create in Port ``` { "identifier": "jsmServiceDesk", "title": "JSM Service desk", "icon": "Service", "description": "A Jira Service Management service desk", "schema": { "properties": { "projectId": { "type": "string", "title": "Project ID" }, "projectKey": { "type": "string", "title": "Project key" }, "projectName": { "type": "string", "title": "Project name" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **JSM Customer request blueprint (click to expand)** Create in Port ``` { "identifier": "jsmCustomerRequest", "title": "JSM Customer request", "icon": "Ticket", "description": "A customer request in Jira Service Management", "schema": { "properties": { "issueKey": { "type": "string", "title": "Issue key" }, "summary": { "type": "string", "title": "Summary" }, "status": { "type": "string", "title": "Status" }, "createdAt": { "type": "string", "title": "Created at (ISO-8601)" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "serviceDesk": { "title": "Service desk", "target": "jsmServiceDesk", "required": false, "many": false } } } ``` **JSM Organization blueprint (click to expand)** Create in Port ``` { "identifier": "jsmOrganization", "title": "JSM Organization", "icon": "Users", "description": "A JSM organization", "schema": { "properties": { "name": { "type": "string", "title": "Name" }, "scimManaged": { "type": "boolean", "title": "SCIM managed" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` 5. Click **Save** after creating each blueprint. ## Configuration[​](#configuration "Direct link to Configuration") Define which API paths sync into Port using [JQ expressions](https://stedolan.github.io/jq/manual/). Map each resource: * **`kind`**: appended to **`baseUrl`**. Use the `/rest/servicedeskapi/...` paths below. * **`selector.data_path`**: `.values` picks the entity array inside JSM paging responses. For how Ocean combines `baseUrl` and pagination, see [How it works](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview#how-it-works). **JSM response shape** Paged list responses follow: ``` { "size": 3, "start": 0, "limit": 50, "isLastPage": false, "_links": { "next": "https://YOUR_DOMAIN.atlassian.net/rest/servicedeskapi/..." }, "values": [ {} ] } ``` Use **`data_path: ".values"`** for each **`kind`** that returns this envelope. **To configure:** 1. Go to your [connectors page](https://app.port.io/settings/data-sources). 2. Find your Jira Service Management integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below (sync service desks before customer requests if you use the service desk relation on requests). **Service desks (click to expand)** **API:** [`GET /rest/servicedeskapi/servicedesk`](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-servicedesk/#api-rest-servicedeskapi-servicedesk-get) ``` resources: - kind: /rest/servicedeskapi/servicedesk selector: query: "true" data_path: ".values" port: entity: mappings: identifier: .id | tostring title: .projectName // .projectKey // .id blueprint: '"jsmServiceDesk"' properties: projectId: .projectId | tostring projectKey: .projectKey projectName: .projectName ``` **Customer requests (click to expand)** **API:** [`GET /rest/servicedeskapi/request`](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-get) ``` resources: - kind: /rest/servicedeskapi/request selector: query: "true" data_path: ".values" port: entity: mappings: identifier: .issueId | tostring title: .summary // .issueKey blueprint: '"jsmCustomerRequest"' properties: issueKey: .issueKey summary: .summary status: .currentStatus.status createdAt: .createdDate.iso8601 relations: serviceDesk: .serviceDeskId | tostring ``` **Organizations (click to expand)** **API:** [`GET /rest/servicedeskapi/organization`](https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-organization/#api-rest-servicedeskapi-organization-get) ``` resources: - kind: /rest/servicedeskapi/organization selector: query: "true" data_path: ".values" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jsmOrganization"' properties: name: .name scimManaged: .scimManaged ``` 5. Click **Save** to apply the mapping. ## Customization[​](#customization "Direct link to Customization") Use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md) to probe live JSM responses, validate JQ, and generate blueprints before you lock in mappings. ## Additional resources[​](#additional-resources "Direct link to Additional resources") * [Jira Service Management Cloud REST API](https://developer.atlassian.com/cloud/jira/service-desk/rest/intro/) (authentication, scopes, pagination). * [No-code Ocean custom integration overview](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) * [No-code Ocean custom integration configuration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/configuration/.md) (mapping options and pagination). --- # Opsgenie ![OpsGenie icon](/img/guides/icons/OpsGenie.svg)![OpsGenie icon](/img/guides/icons/OpsGenie.svg) Loading version... Port's Opsgenie integration allows you to model Opsgenie resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Alert](https://docs.opsgenie.com/docs/alert-api#list-alerts)[Incident](https://docs.opsgenie.com/docs/incident-api#list-incidents)[Service](https://docs.opsgenie.com/docs/service-api#list-services)[User](https://docs.opsgenie.com/docs/user-api#list-user)[Team](https://docs.opsgenie.com/docs/team-api#list-teams)[Schedule](https://docs.opsgenie.com/docs/schedule-api#list-schedules)[Schedule-Oncall](https://docs.opsgenie.com/docs/who-is-on-call-api#get-on-calls) ## Core support [Live eventsSee supported resources](#configure-real-time-updates) ## Overview[​](#overview "Direct link to Overview") This integration allows you to: * Map and organize your desired Opsgenie resources and their metadata in Port (see supported resources below). * Watch for Opsgenie object changes (create/update/delete) in real-time, and automatically apply the changes to your entities in Port. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Configure real-time updates[​](#configure-real-time-updates "Direct link to Configure real-time updates") Currently, the OpsGenie API lacks support for programmatic webhook creation. To set up a webhook configuration in OpsGenie for sending alert notifications to the Ocean integration, follow these steps: #### Prerequisites[​](#prerequisites "Direct link to Prerequisites") Prepare a webhook `URL` using this format: `{app_host}/integration/webhook`. The `app_host` parameter should match the ingress or external load balancer where the integration will be deployed. For example, if your ingress or load balancer exposes the OpsGenie Ocean integration at `https://myservice.domain.com`, your webhook `URL` should be `https://myservice.domain.com/integration/webhook`. #### Create a webhook in OpsGenie[​](#create-a-webhook-in-opsgenie "Direct link to Create a webhook in OpsGenie") 1. Go to OpsGenie. 2. Select **Settings**. 3. Click on **Integrations** under the **Integrations** section of the sidebar. 4. Click on **Add integration**. 5. In the search box, type *Webhook* and select the webhook option. 6. Input the following details: 1. `Name` - use a meaningful name such as Port Ocean Webhook. 2. Be sure to keep the "Enabled" checkbox checked. 3. Check the "Add Alert Description to Payload" checkbox. 4. Check the "Add Alert Details to Payload" checkbox. 5. Add the following action triggers to the webhook by clicking on **Add new action**: 1. If *alert is snoozed* in Opsgenie, *post to url* in Webhook. 2. If *alert's description is updated* in Opsgenie, *post to url* in Webhook. 3. If *alert's message is updated* in Opsgenie, *post to url* in Webhook. 4. If *alert's priority is updated* in Opsgenie, *post to url* in Webhook. 5. If *a responder is added to the alert* in Opsgenie, *post to url* in Webhook. 6. if *a user executes "Assign Ownership* in Opsgenie, *post to url* in Webhook. 7. if *a tag is added to the alert* in Opsgenie, *post to url* in Webhook. 8. .if *a tag is removed from the alert* in Opsgenie, *post to url* in Webhook. 6. `Webhook URL` - enter the value of the `URL` you created above. 7. Click **Save integration**. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: team selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieTeam"' properties: description: .description url: .links.web - kind: schedule selector: query: 'true' apiQueryParams: expand: rotation port: entity: mappings: identifier: .id + "_" + .item.id title: .name + "_" + .item.name blueprint: '"opsGenieSchedule"' properties: timezone: .timezone description: .description startDate: .item.startDate endDate: .item.endDate rotationType: .item.type relations: owner_opsgenie_team: .ownerTeam.id schedule_opsgenie_users: '[.item.participants[] | select(has("username")) | .username]' schedule_users: combinator: '"and"' rules: - property: '"_opsGenieUserId"' operator: '"in"' value: '[.item.participants[] | select(has("username")) | .username]' itemsToParse: .rotations - kind: service selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieService"' properties: description: .description url: .links.web tags: .tags relations: owner_opsgenie_team: .teamId - kind: alert selector: query: 'true' apiQueryParams: status: open port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieAlert"' properties: status: .status acknowledged: .acknowledged priority: .priority sourceName: .source tags: .tags count: .count createdBy: .owner createdAt: .createdAt updatedAt: .updatedAt description: .description integration: .integration.name relations: relatedIncident: if (.alias | contains("_")) then (.alias | split("_")[0]) else null end responding_opsgenie_team: .responders | [.[] | select(.type == "team") | .id] responding_team: combinator: '"and"' rules: - property: '"opsgenie_team_id"' operator: '"in"' value: .responders | [.[] | select(.type == "team") | .id] responding_opsgenie_user: .responders | [.[] | select(.type == "user") | .id] responding_user: combinator: '"and"' rules: - property: '"_opsGenieUserId"' operator: '"in"' value: .responders | [.[] | select(.type == "user") | .id] - kind: incident selector: query: 'true' apiQueryParams: status: open port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieIncident"' properties: status: .status priority: .priority tags: .tags url: .links.web createdAt: .createdAt updatedAt: .updatedAt description: .description relations: impacted_opsgenie_services: .impactedServices impacted_services: combinator: '"and"' rules: - property: '"_opsGenieServiceId"' operator: '"in"' value: .impactedServices responding_opsgenie_team: .responders | [.[] | select(.type == "team") | .id] responding_team: combinator: '"and"' rules: - property: '"opsgenie_team_id"' operator: '"in"' value: .responders | [.[] | select(.type == "team") | .id] responding_opsgenie_user: .responders | [.[] | select(.type == "user") | .id] responding_user: combinator: '"and"' rules: - property: '"_opsGenieUserId"' operator: '"in"' value: .responders | [.[] | select(.type == "user") | .id] - kind: schedule-oncall selector: query: 'true' port: entity: mappings: identifier: .ownerTeam.id title: .ownerTeam.name blueprint: '"opsGenieTeam"' properties: oncallUsers: .__currentOncalls.onCallRecipients relations: schedule_opsgenie_users: .__currentOncalls.onCallRecipients schedule_users: combinator: '"and"' rules: - property: '"_opsGenieUserId"' operator: '"in"' value: .__currentOncalls.onCallRecipients - kind: user selector: query: 'true' port: entity: mappings: identifier: .id title: .fullName blueprint: '"opsGenieUser"' properties: email: .username role: .role.name timeZone: .timeZone isVerified: .verified isBlocked: .blocked address: .userAddress createdAt: .createdAt ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/examples/.md). AlertIncidentServiceUserTeamScheduleSchedule-Oncall Default mappingYAML ``` - kind: alert selector: query: 'true' apiQueryParams: status: open port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieAlert"' properties: status: .status acknowledged: .acknowledged priority: .priority sourceName: .source tags: .tags count: .count createdBy: .owner createdAt: .createdAt updatedAt: .updatedAt description: .description integration: .integration.name relations: relatedIncident: if (.alias | contains("_")) then (.alias | split("_")[0]) else null end responding_opsgenie_team: .responders | [.[] | select(.type == "team") | .id] responding_team: combinator: '"and"' rules: - property: '"opsgenie_team_id"' operator: '"in"' value: .responders | [.[] | select(.type == "team") | .id] responding_opsgenie_user: .responders | [.[] | select(.type == "user") | .id] responding_user: combinator: '"and"' rules: - property: '"_opsGenieUserId"' operator: '"in"' value: .responders | [.[] | select(.type == "user") | .id] ``` Sample payloadJSON ``` { "seen": true, "id": "886b285b-b35e-487b-afe6-3d5001370363-1724857097467", "tinyId": "13", "alias": "355f1681-f6e3-4355-a927-897cd8edaf8f_886b285b-b35e-487b-afe6-3d5001370363-1724857097467", "message": "Test scaling alerts", "status": "open", "acknowledged": false, "isSeen": true, "tags": [], "snoozed": false, "count": 1, "lastOccurredAt": "2024-08-28T14:58:17.467Z", "createdAt": "2024-08-28T14:58:17.467Z", "updatedAt": "2024-08-28T14:58:59.285Z", "source": "dev@domain.com", "owner": "", "priority": "P3", "teams": [], "responders": [ { "type": "user", "id": "ce544b61-7b35-43ea-89ee-a8750638d3a4" } ], "integration": { "id": "00c228b7-80a4-48ae-be9c-3a1471bad69d", "name": "Default API", "type": "API" }, "ownerTeamId": "" } ``` Port entityJSON ``` { "identifier": "886b285b-b35e-487b-afe6-3d5001370363-1724857097467", "title": "Test scaling alerts", "icon": "OpsGenie", "blueprint": "opsGenieAlert", "team": [], "properties": { "description": null, "status": "open", "acknowledged": false, "tags": [], "responders": [ { "id": "ce544b61-7b35-43ea-89ee-a8750638d3a4", "type": "user" } ], "integration": "Default API", "priority": "P3", "sourceName": "dev@domain.com", "createdBy": "", "createdAt": "2024-08-28T14:58:17.467Z", "updatedAt": "2024-08-28T14:58:59.285Z", "count": 1 }, "relations": { "relatedIncident": "355f1681-f6e3-4355-a927-897cd8edaf8f" }, "createdAt": "2024-08-29T10:31:34.381Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-08-29T10:31:34.381Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | The query parameters to filter alerts or incidents | `apiQueryParams` parameters | Parameter | Type | Description | | ---------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `createdAt` | `string` | The date and time the alert or incident was created (e.g. 2024-01-31T12:00:00Z) | | `lastOccurredAt` | `string` | The date and time the alert was last occurred (e.g. 2024-01-31T12:00:00Z) | | `snoozedUntil` | `string` | The date and time the alert was snoozed until (e.g. 2024-01-31T12:00:00Z) | | `message` | `string` | The message of the alert or incident | | `status` | `string` | The status of the alert Allowed values: `open`, `resolved`, `closed`. | | `is_seen` | `boolean` | Whether the alert has been seen | | `acknowledged` | `boolean` | Whether the alert has been acknowledged | | `snoozed` | `boolean` | Whether the alert has been snoozed | | `priority` | `string` | The priority of the alert Allowed values: `P1`, `P2`, `P3`, `P4`, `P5`. | | `owner` | `string` | The owner of the alert. Accepts OpsGenie username | | `teams` | `string` | The teams associated with the alert. Accepts a comma-separated string of team names (e.g. 'team-a, team-b') | | `acknowledgedBy` | `string` | The user who acknowledged the alert | | `closedBy` | `string` | The user who closed the alert | Default mappingYAML ``` - kind: incident selector: query: 'true' apiQueryParams: status: open port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieIncident"' properties: status: .status priority: .priority tags: .tags url: .links.web createdAt: .createdAt updatedAt: .updatedAt description: .description relations: impacted_opsgenie_services: .impactedServices impacted_services: combinator: '"and"' rules: - property: '"_opsGenieServiceId"' operator: '"in"' value: .impactedServices responding_opsgenie_team: .responders | [.[] | select(.type == "team") | .id] responding_team: combinator: '"and"' rules: - property: '"opsgenie_team_id"' operator: '"in"' value: .responders | [.[] | select(.type == "team") | .id] responding_opsgenie_user: .responders | [.[] | select(.type == "user") | .id] responding_user: combinator: '"and"' rules: - property: '"_opsGenieUserId"' operator: '"in"' value: .responders | [.[] | select(.type == "user") | .id] ``` Sample payloadJSON ``` { "id": "652f14b3-019a-4d4c-8b83-e4da527a416c", "description": "summary", "impactedServices": [ "daa0d66f-ad35-4396-b30d-70f0314c697a" ], "tinyId": "3", "message": "OpenAI Token Incident", "status": "open", "tags": [ "incident" ], "createdAt": "2023-09-26T17:06:16.824Z", "updatedAt": "2023-09-26T17:49:10.17Z", "priority": "P3", "ownerTeam": "", "responders": [ { "type": "team", "id": "63374eee-0b03-42d4-bb8c-50d1fa64827c" }, { "type": "user", "id": "ce544b61-7b35-43ea-89ee-a8750638d3a4" } ], "extraProperties": {}, "links": { "web": "https://mydomain.app.opsgenie.com/incident/detail/652f14b3-019a-4d4c-8b83-e4da527a416c", "api": "https://api.opsgenie.com/v1/incidents/652f14b3-019a-4d4c-8b83-e4da527a416c" }, "impactStartDate": "2023-09-26T17:06:16.824Z", "impactEndDate": "2023-09-26T17:45:25.719Z", "actions": [] } ``` Port entityJSON ``` { "identifier": "652f14b3-019a-4d4c-8b83-e4da527a416c", "title": "OpenAI Token Incident", "icon": "OpsGenie", "blueprint": "opsGenieIncident", "team": [], "properties": { "description": "summary", "status": "open", "url": "https://mydomain.app.opsgenie.com/incident/detail/652f14b3-019a-4d4c-8b83-e4da527a416c", "tags": [ "tags" ], "responders": [ { "id": "63374eee-0b03-42d4-bb8c-50d1fa64827c", "type": "team" }, { "id": "ce544b61-7b35-43ea-89ee-a8750638d3a4", "type": "user" } ], "priority": "P3", "createdAt": "2023-09-26T17:06:16.824Z", "updatedAt": "2023-09-26T17:49:10.17Z" }, "relations": { "services": [ "daa0d66f-ad35-4396-b30d-70f0314c697a" ] }, "createdAt": "2024-08-28T16:58:15.414Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-08-28T16:58:15.414Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | The query parameters to filter alerts or incidents | `apiQueryParams` parameters | Parameter | Type | Description | | ---------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `createdAt` | `string` | The date and time the alert or incident was created (e.g. 2024-01-31T12:00:00Z) | | `lastOccurredAt` | `string` | The date and time the alert was last occurred (e.g. 2024-01-31T12:00:00Z) | | `snoozedUntil` | `string` | The date and time the alert was snoozed until (e.g. 2024-01-31T12:00:00Z) | | `message` | `string` | The message of the alert or incident | | `status` | `string` | The status of the alert Allowed values: `open`, `resolved`, `closed`. | | `is_seen` | `boolean` | Whether the alert has been seen | | `acknowledged` | `boolean` | Whether the alert has been acknowledged | | `snoozed` | `boolean` | Whether the alert has been snoozed | | `priority` | `string` | The priority of the alert Allowed values: `P1`, `P2`, `P3`, `P4`, `P5`. | | `owner` | `string` | The owner of the alert. Accepts OpsGenie username | | `teams` | `string` | The teams associated with the alert. Accepts a comma-separated string of team names (e.g. 'team-a, team-b') | | `acknowledgedBy` | `string` | The user who acknowledged the alert | | `closedBy` | `string` | The user who closed the alert | Default mappingYAML ``` - kind: service selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieService"' properties: description: .description url: .links.web tags: .tags relations: owner_opsgenie_team: .teamId ``` Sample payloadJSON ``` { "id": "96856ebc-1db0-497b-b90a-5172e6ca0cb3", "name": "Pricing Service", "description": "Product pricing algorithm service", "teamId": "63374eee-0b03-42d4-bb8c-50d1fa64827c", "tags": [ "frontend" ], "links": { "web": "https://mydomain.app.opsgenie.com/service/96856ebc-1db0-497b-b90a-5172e6ca0cb3/status", "api": "https://api.opsgenie.com/v1/services/96856ebc-1db0-497b-b90a-5172e6ca0cb3" }, "isExternal": false } ``` Port entityJSON ``` { "identifier": "96856ebc-1db0-497b-b90a-5172e6ca0cb3", "title": "Pricing Service", "icon": "OpsGenie", "blueprint": "opsGenieService", "team": [], "properties": { "description": "Product pricing algorithm service", "url": "https://mydomain.app.opsgenie.com/service/96856ebc-1db0-497b-b90a-5172e6ca0cb3/status", "tags": [ "frontend" ], "oncallUsers": [ "dev@domain.com" ], "numberOfOpenIncidents": null }, "relations": { "ownerTeam": null }, "createdAt": "2024-09-02T21:02:01.207Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-09-02T21:02:01.207Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: .id title: .fullName blueprint: '"opsGenieUser"' properties: email: .username role: .role.name timeZone: .timeZone isVerified: .verified isBlocked: .blocked address: .userAddress createdAt: .createdAt ``` Sample payloadJSON ``` { "blocked": false, "verified": true, "id": "45d6-b25e-6a7aae22423b-3e28d924-840d", "username": "jane.doe@gmail.com", "fullName": "Jane Doe", "role": { "id": "Admin", "name": "Admin" }, "timeZone": "Asia/Jerusalem", "locale": "en_US", "userAddress": { "country": "", "state": "", "city": "", "line": "", "zipCode": "" }, "createdAt": "2023-09-26T07:57:24.246Z" } ``` Port entityJSON ``` { "identifier": "45d6-b25e-6a7aae22423b-3e28d924-840d", "title": "Jane Doe", "icon": "OpsGenie", "blueprint": "opsGenieUser", "team": [], "properties": { "email": "jane.doe@gmail.com", "role": "Admin", "timeZone": "Asia/Jerusalem", "isVerified": true, "isBlocked": false, "address": { "country": "", "state": "", "city": "", "line": "", "zipCode": "" }, "createdAt": "2023-09-26T07:57:24.246Z" }, "relations": {}, "createdAt": "2024-10-02T21:01:40.937Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-10-02T21:01:40.937Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: team selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieTeam"' properties: description: .description url: .links.web ``` Sample payloadJSON ``` { "id": "63374eee-0b03-42d4-bb8c-50d1fa64827c", "name": "Data Science Team", "description": "", "links": { "web": "https://app.opsgenie.com/teams/dashboard/63374eee-0b03-42d4-bb8c-50d1fa64827c/main", "api": "https://api.opsgenie.com/v2/teams/63374eee-0b03-42d4-bb8c-50d1fa64827c" } } ``` Port entityJSON ``` { "identifier": "63374eee-0b03-42d4-bb8c-50d1fa64827c", "title": "Data Science Team", "icon": "OpsGenie", "blueprint": "opsGenieTeam", "team": [], "properties": { "description": "", "url": "https://app.opsgenie.com/teams/dashboard/63374eee-0b03-42d4-bb8c-50d1fa64827c/main" }, "relations": {}, "createdAt": "2024-09-02T21:01:40.937Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-09-02T21:01:40.937Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `includeMembers` | `boolean` | Whether to include the members of the team, defaults to false Default: `false`. | Default mappingYAML ``` - kind: schedule selector: query: 'true' apiQueryParams: expand: rotation port: entity: mappings: identifier: .id + "_" + .item.id title: .name + "_" + .item.name blueprint: '"opsGenieSchedule"' properties: timezone: .timezone description: .description startDate: .item.startDate endDate: .item.endDate rotationType: .item.type relations: owner_opsgenie_team: .ownerTeam.id schedule_opsgenie_users: '[.item.participants[] | select(has("username")) | .username]' schedule_users: combinator: '"and"' rules: - property: '"_opsGenieUserId"' operator: '"in"' value: '[.item.participants[] | select(has("username")) | .username]' itemsToParse: .rotations ``` Sample payloadJSON ``` { "item": { "id": "c4fa16f1-8675-4a26-9c2a-7b7a0c98a9cb", "name": "Rota2", "startDate": "2024-09-02T08:00:00Z", "endDate": "2024-09-14T09:00:00Z", "type": "weekly", "length": 3, "participants": [ { "type": "user", "id": "ce544b61-7b35-43ea-89ee-a8750638d3a4", "username": "dev@domain.com" } ], "timeRestriction": null }, "id": "977805c9-ede6-4cc1-a535-93e93767a436", "name": "Devops Team_schedule", "description": "", "timezone": "Africa/Monrovia", "enabled": true, "ownerTeam": { "id": "bae765bb-a288-4731-b826-b2c65ff16f24", "name": "Devops Team" }, "rotations": [ { "id": "c4fa16f1-8675-4a26-9c2a-7b7a0c98a9cb", "name": "Rota2", "startDate": "2024-09-02T08:00:00Z", "endDate": "2024-09-14T09:00:00Z", "type": "weekly", "length": 3, "participants": [ { "type": "user", "id": "ce544b61-7b35-43ea-89ee-a8750638d3a4", "username": "dev@domain.com" } ], "timeRestriction": null }, { "id": "1fbeb5b1-4e00-483e-ab01-323881535159", "name": "Rota1", "startDate": "2024-09-02T08:00:00Z", "endDate": "2024-09-10T09:00:00Z", "type": "weekly", "length": 1, "participants": [ { "type": "user", "id": "ce544b61-7b35-43ea-89ee-a8750638d3a4", "username": "dev@domain.com" } ], "timeRestriction": null } ] } ``` Port entityJSON ``` { "identifier": "d55e148e-d320-4766-9dcf-4fc2ce9daef1_fefc6b88-e44d-4acc-93a2-daf3ba803690", "title": "Data Science Team_schedule_Rota3", "icon": "OpsGenie", "blueprint": "opsGenieSchedule", "team": [], "properties": { "timezone": "Africa/Monrovia", "description": "", "users": [], "startDate": "2024-09-02T08:00:00Z", "endDate": "2024-10-02T08:00:00Z", "rotationType": "daily" }, "relations": { "ownerTeam": "63374eee-0b03-42d4-bb8c-50d1fa64827c" }, "createdAt": "2024-09-10T12:24:02.999Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-09-10T12:24:02.999Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | The query parameters to filter schedules | `apiQueryParams` parameters | Parameter | Type | Description | | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `expand` | `string` | When set to 'rotation', the schedule response will include the full rotation details instead of just a reference Allowed values: `rotation`. | Default mappingYAML ``` - kind: schedule-oncall selector: query: 'true' port: entity: mappings: identifier: .ownerTeam.id title: .ownerTeam.name blueprint: '"opsGenieTeam"' properties: oncallUsers: .__currentOncalls.onCallRecipients relations: schedule_opsgenie_users: .__currentOncalls.onCallRecipients schedule_users: combinator: '"and"' rules: - property: '"_opsGenieUserId"' operator: '"in"' value: .__currentOncalls.onCallRecipients ``` Sample payloadJSON ``` { "id": "977805c9-ede6-4cc1-a535-93e93767a436", "name": "Devops Team_schedule", "description": "", "timezone": "Africa/Monrovia", "enabled": true, "ownerTeam": { "id": "bae765bb-a288-4731-b826-b2c65ff16f24", "name": "Devops Team" }, "rotations": [], "__currentOncalls": { "_parent": { "id": "977805c9-ede6-4cc1-a535-93e93767a436", "name": "Devops Team_schedule", "enabled": true }, "onCallRecipients": [ "dev@domain.com" ] } } ``` Port entityJSON ``` { "identifier": "63374eee-0b03-42d4-bb8c-50d1fa64827c", "title": "Data Science Team", "icon": "OpsGenie", "blueprint": "opsGenieTeam", "team": [], "properties": { "description": "", "url": "https://app.opsgenie.com/teams/dashboard/63374eee-0b03-42d4-bb8c-50d1fa64827c/main", "oncallUsers": [ "janedoe@devportal.io", "johnsmith@gmail.com", "dev@domain.com" ] }, "relations": {}, "createdAt": "2024-09-02T21:01:40.937Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-09-10T11:41:09.535Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Limitations[​](#limitations "Direct link to Limitations") ### Maximum offset in OpsGenie APIs[​](#maximum-offset-in-opsgenie-apis "Direct link to Maximum offset in OpsGenie APIs") The OpsGenie APIs for [Alerts](https://docs.opsgenie.com/docs/alert-api#list-alerts) and [Incidents](https://docs.opsgenie.com/docs/incident-api#list-incidents) have a pagination limit where the sum of `offset` and `limit` parameters cannot exceed 20,000 records. This means you can retrieve up to 20,000 records in a single query session for each resource type. Working with large datasets To access records beyond the 20,000 limit, use filters to narrow your result set. This approach reduces the number of records returned and helps you bypass the offset limitation. For implementation details and supported filter options, see the [examples](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/examples/.md#alert) section. ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found on the opsgenie [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/examples/.md) ## Alternative installation via webhook[​](#alternative-installation-via-webhook "Direct link to Alternative installation via webhook") While the Ocean integration described above is the recommended installation method, you may prefer to use a webhook to ingest data from Opsgenie. If so, use the following instructions: **Webhook installation (click to expand)** In this example you are going to create a webhook integration between [OpsGenie](https://www.atlassian.com/software/opsgenie) and Port, which will ingest alert entities. ### Port configuration[​](#port-configuration "Direct link to Port configuration") Create the following blueprint definition: **OpsGenie alert blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieAlert", "description": "This blueprint represents an OpsGenie alert in our software catalog", "title": "OpsGenie Alert", "icon": "OpsGenie", "schema": { "properties": { "description": { "type": "string", "title": "Description" }, "lastChangeType": { "type": "string", "title": "Last Change Type", "description": "The type of the last change made to the alert" }, "tags": { "type": "array", "items": { "type": "string" }, "title": "Tags" }, "responders": { "type": "array", "title": "Responders", "description": "Responders to the alert" }, "teams": { "type": "array", "items": { "type": "string" }, "title": "Teams", "description": "IDs of teams assigned to the alert" }, "priority": { "type": "string", "title": "Priority" }, "sourceName": { "type": "string", "title": "Source Name", "description": "Alert source name" }, "sourceType": { "type": "string", "title": "Source Type", "description": "Alert source type" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": { "status": { "title": "Status", "calculation": "if .properties.lastChangeType == \"Close\" then \"Closed\" else \"Active\" end", "type": "string", "colorized": true, "colors": { "Closed": "green", "Active": "red" } } }, "relations": {} } ``` Create the following webhook configuration [using Port UI](/context-lake/ingestion/ingest-data-into-port/webhook/ingest-via-webhooks-overview/.md?operation=ui#configuring-webhook-endpoints): **OpsGenie alert webhook configuration (click to expand)** 1. **Basic details** tab - fill the following details: 1. Title : `OpsGenie mapper`. 2. Identifier : `opsgenie_mapper`. 3. Description : `A webhook configuration to map OpsGenie alerts to Port`. 4. Icon : `OpsGenie`. 2. **Integration configuration** tab - fill the following JQ mapping: ``` [ { "blueprint": "opsGenieAlert", "entity": { "identifier": ".body.alert.alertId", "title": ".body.alert.tinyId + \" - \" + .body.alert.message", "properties": { "description": ".body.alert.description", "lastChangeType": ".body.action", "priority": ".body.alert.priority", "sourceName": ".body.source.name", "sourceType": ".body.source.type", "tags": ".body.alert.tags", "responders": ".body.alert.responders", "teams": ".body.alert.teams" } } } ] ``` 3. Click **Save** at the bottom of the page. ## Create a webhook in OpsGenie 1. Go to OpsGenie. 2. Select **Settings**. 3. Click on **Integrations** under the **Integrations** section of the sidebar. 4. Click on **Add integration**. 5. In the search box, type *Webhook* and select the webhook option. 6. Input the following details: 1. `Name` - use a meaningful name such as Port Webhook. 2. Be sure to keep the "Enabled" checkbox checked. 3. Check the "Add Alert Description to Payload" checkbox. 4. Check the "Add Alert Details to Payload" checkbox. 5. Add the following action triggers to the webhook by clicking on **Add new action**: 1. If *alert is snoozed* in Opsgenie, *post to url* in Webhook. 2. If *alert's description is updated* in Opsgenie, *post to url* in Webhook. 3. If *alert's message is updated* in Opsgenie, *post to url* in Webhook. 4. If *alert's priority is updated* in Opsgenie, *post to url* in Webhook. 5. If *a responder is added to the alert* in Opsgenie, *post to url* in Webhook. 6. if *a user executes "Assign Ownership* in Opsgenie, *post to url* in Webhook. 7. if *a tag is added to the alert* in Opsgenie, *post to url* in Webhook. 8. .if *a tag is removed from the alert* in Opsgenie, *post to url* in Webhook. 6. `Webhook URL` - enter the value of the `url` key you received after creating the webhook configuration. 7. Click **Save integration** View available Opsgenie webhook payloads In order to view the different payloads and events available in Opsgenie webhooks, [look here](https://support.atlassian.com/opsgenie/docs/opsgenie-edge-connector-alert-action-data/) Done! any change that happens to an OpsGenie alert (created, acknowledged, etc.) will trigger a webhook event that OpsGenie will send to the webhook URL provided by Port. Port will parse the events according to the mapping and update the catalog entities accordingly. ## Let's Test It This section includes a sample webhook event sent from OpsGenie when an alert is created. In addition, it includes the entity created from the event based on the webhook configuration provided in the previous section. ### Payload Here is an example of the payload structure sent to the webhook URL when an OpsGenie alert is created: **Webhook event payload (click to expand)** ``` { "source": { "name": "web", "type": "API" }, "alert": { "tags": ["tag1", "tag2"], "teams": ["team1", "team2"], "responders": ["recipient1", "recipient2"], "message": "test alert", "username": "username", "alertId": "052652ac-5d1c-464a-812a-7dd18bbfba8c", "source": "user@domain.com", "alias": "aliastest", "tinyId": "10", "entity": "An example entity", "createdAt": 1686916265415, "updatedAt": 1686916266116, "userId": "daed1180-0ce8-438b-8f8e-57e1a5920a2d", "description": "Testing opsgenie alerts", "priority": "P1" }, "action": "Create", "integrationId": "37c8f316-17c6-49d7-899b-9c7e540c048d", "integrationName": "Port-Integration" } ``` ### Mapping Result The combination of the sample payload and the webhook configuration generates the following Port entity: ``` { "identifier": "052652ac-5d1c-464a-812a-7dd18bbfba8c", "title": "10 - test alert", "blueprint": "opsGenieAlert", "properties": { "description": "Testing opsgenie alerts", "lastChangeType": "Create", "priority": "P1", "sourceName": "web", "sourceType": "API", "tags": ["tag1", "tag2"], "responders": ["recipient1", "recipient2"], "teams": ["team1", "team2"] }, "relations": {} } ``` ## Ingest who is on-call In this example we will create a blueprint for `service` entities with an `on-call` property that will be ingested directly from OpsGenie. The examples below pull data from the OpsGenie REST Api, in a defined scheduled period using GitLab Pipelines or GitHub Workflows, and report the data to Port as a property to the `service` blueprint. * [Github Workflow](https://github.com/port-labs/opsgenie-oncall-example) * [GitLab CI Pipeline](https://gitlab.com/getport-labs/opsgenie-oncall-example) ## Migration Guide to Version 0.2.0[​](#migration-guide-to-version-020 "Direct link to Migration Guide to Version 0.2.0") This guide outlines how to update your existing OpsGenie integration configuration to take advantage of the performance improvements and breaking changes introduced in version 0.2.0. ### Key Improvements[​](#key-improvements "Direct link to Key Improvements") * **New Blueprints and Kinds**: Added new kinds for team, schedule, and schedule-oncall. * **Data Filtering**: Introduced support for filtering data from the OpsGenie API to fetch only the necessary information. * **Enhanced Logging**: Added logs for easier debugging and better insight into integration issues. ### Breaking Changes[​](#breaking-changes "Direct link to Breaking Changes") * **Optimized API Calls**: Removed redundant API calls for fetching impacted services and alert-incident relationships, improving performance by leveraging existing relations and JQ * **Blueprint Changes**: The `OpsGenieService` blueprint no longer contains team properties like `oncallTeam`, `teamMembers`, and `oncallUsers`. These have been moved to a new `OpsGenieTeam` blueprint, reflecting a new relation between services and teams. ### Migration Steps[​](#migration-steps "Direct link to Migration Steps") #### Step 1: Understand Existing Configuration In versions prior to 0.2.0, your Port app's configuration may have used a mapping like the one below: **Existing configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: service selector: query: 'true' port: entity: mappings: identifier: .name | gsub("[^a-zA-Z0-9@_.:/=-]"; "-") | tostring title: .name blueprint: '"opsGenieService"' properties: description: .description url: .links.web tags: .tags oncallTeam: .__team.name teamMembers: '[.__team.members[].user.username]' oncallUsers: .__oncalls.onCallRecipients - kind: alert selector: query: 'true' port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieAlert"' properties: status: .status acknowledged: .acknowledged responders: .responders priority: .priority sourceName: .source tags: .tags count: .count createdBy: .owner createdAt: .createdAt updatedAt: .updatedAt description: .description integration: .integration.name relations: relatedIncident: .__relatedIncident.id - kind: incident selector: query: 'true' port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieIncident"' properties: status: .status responders: .responders priority: .priority tags: .tags url: .links.web createdAt: .createdAt updatedAt: .updatedAt description: .description relations: services: '[.__impactedServices[] | .name | gsub("[^a-zA-Z0-9@_.:/=-]"; "-") | tostring]' ``` #### Step 2: Update to New Configuration To adapt to version 0.2.0, you will need to update your configuration as follows: **New configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: service selector: query: 'true' port: entity: mappings: identifier: .id # The identifier of the service now uses the unique ID from OpsGenie instead of the name title: .name blueprint: '"opsGenieService"' properties: description: .description url: .links.web tags: .tags - kind: alert selector: query: 'true' port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieAlert"' properties: status: .status acknowledged: .acknowledged responders: .responders priority: .priority sourceName: .source tags: .tags count: .count createdBy: .owner createdAt: .createdAt updatedAt: .updatedAt description: .description integration: .integration.name relations: relatedIncident: 'if (.alias | contains("_")) then (.alias | split("_")[0]) else null end' # We now use JQ logic to map alerts to incidents to avoid making extra API call - kind: incident selector: query: 'true' port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieIncident"' properties: status: .status responders: .responders priority: .priority tags: .tags url: .links.web createdAt: .createdAt updatedAt: .updatedAt description: .description relations: services: .impactedServices # We can now directly map incidents to impacted services using the data that is coming from the API ``` In the updated configuration, the `opsGenieService` blueprint no longer includes properties like `oncallTeam`, `teamMembers`, and `oncallUsers`. These properties are now part of the new `OpsGenieTeam` blueprint. If you need to track on-call teams and users for each service, follow the steps below. #### Step 3: Create the OpsGenieTeam Blueprint To manage team-related data, create a new `OpsGenieTeam` blueprint in Port using the following schema: **OpsGenie team blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieTeam", "description": "This blueprint represents an OpsGenie team in our software catalog", "title": "OpsGenie Team", "icon": "OpsGenie", "schema": { "properties": { "description": { "type": "string", "title": "Description", "icon": "DefaultProperty" }, "url": { "title": "URL", "type": "string", "description": "URL to the service", "format": "url", "icon": "DefaultProperty" }, "oncallUsers": { "type": "array", "title": "Current Oncalls", "items": { "type": "string", "format": "user" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` #### Step 4: Update the OpsGenieService Blueprint Next, update the `opsGenieService` blueprint to reference the `OpsGenieTeam` blueprint by establishing a relation and mirroring relevant properties: **Updated OpsGenie service blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieService", "description": "This blueprint represents an OpsGenie service in our software catalog", "title": "OpsGenie Service", "icon": "OpsGenie", "schema": { "properties": { "description": { "type": "string", "title": "Description", "icon": "DefaultProperty" }, "url": { "title": "URL", "type": "string", "description": "URL to the service", "format": "url", "icon": "DefaultProperty" }, "tags": { "type": "array", "items": { "type": "string" }, "title": "Tags", "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": { "oncallUsers": { "title": "Current Oncalls", "path": "ownerTeam.oncallUsers" } }, "calculationProperties": { }, "aggregationProperties": { "numberOfOpenIncidents": { "title": "Number of open incidents", "type": "number", "target": "opsGenieIncident", "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "open" } ] }, "calculationSpec": { "calculationBy": "entities", "func": "count" } } }, "relations": { "ownerTeam": { "title": "Owner Team", "target": "opsGenieTeam", "required": false, "many": false } } } ``` #### Step 5: Update the Mapping Configuration Update your configuration mapping to correctly populate the `OpsGenieTeam` blueprint with team and on-call data. This will enable you to view on-call team information at the service level: **Updated configuration to add teams and oncalls (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: team selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieTeam"' properties: description: .description url: .links.web - kind: service selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieService"' properties: description: .description url: .links.web tags: .tags relations: ownerTeam: .teamId - kind: schedule-oncall selector: query: 'true' port: entity: mappings: identifier: .ownerTeam.id title: .ownerTeam.name blueprint: '"opsGenieTeam"' properties: oncallUsers: .__currentOncalls.onCallRecipients ``` #### Final Step: Full Configuration Example After completing these changes, your configuration should look like this, incorporating blueprints for `team`, `service`, `alert` and `incident`: **Full configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: team selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieTeam"' properties: description: .description url: .links.web - kind: service selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieService"' properties: description: .description url: .links.web tags: .tags relations: ownerTeam: .teamId - kind: alert selector: query: 'true' port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieAlert"' properties: status: .status acknowledged: .acknowledged responders: .responders priority: .priority sourceName: .source tags: .tags count: .count createdBy: .owner createdAt: .createdAt updatedAt: .updatedAt description: .description integration: .integration.name relations: relatedIncident: 'if (.alias | contains("_")) then (.alias | split("_")[0]) else null end' - kind: incident selector: query: 'true' port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieIncident"' properties: status: .status responders: .responders priority: .priority tags: .tags url: .links.web createdAt: .createdAt updatedAt: .updatedAt description: .description relations: services: .impactedServices - kind: schedule-oncall selector: query: 'true' port: entity: mappings: identifier: .ownerTeam.id title: .ownerTeam.name blueprint: '"opsGenieTeam"' properties: oncallUsers: .__currentOncalls.onCallRecipients ``` Following this guide will ensure your integration is up-to-date and optimized for performance with version 0.2.0. For any issues during the migration, refer to the newly introduced debug logs to identify and contact your support personnel to resolve problems efficiently. ## More relevant guides and examples[​](#more-relevant-guides-and-examples "Direct link to More relevant guides and examples") * [Self-service action to trigger an OpsGenie incident](/workflows/actions-and-automations/setup-backend/webhook/port-execution-agent/examples/trigger-opsgenie-incident/.md). --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ## User[​](#user "Direct link to User") **User blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieUser", "description": "This blueprint represents an OpsGenie user in our software catalog", "title": "OpsGenie User", "icon": "OpsGenie", "schema": { "properties": { "role": { "title": "Role", "type": "string" }, "email": { "type": "string", "title": "Email", "format": "user" }, "address": { "type": "object", "title": "Address" }, "timeZone": { "type": "string", "title": "Time Zone" }, "isVerified": { "type": "boolean", "title": "Is Verified" }, "isBlocked": { "type": "boolean", "title": "Is Blocked" }, "createdAt": { "type": "string", "title": "Created At", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: user selector: query: 'true' port: entity: mappings: identifier: .id title: .fullName blueprint: '"opsGenieUser"' properties: email: .username role: .role.name timeZone: .timeZone isVerified: .verified isBlocked: .blocked address: .userAddress createdAt: .createdAt ``` ## Team[​](#team "Direct link to Team") **Team blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieTeam", "description": "This blueprint represents an OpsGenie team in our software catalog", "title": "OpsGenie Team", "icon": "OpsGenie", "schema": { "properties": { "description": { "type": "string", "title": "Description", "icon": "DefaultProperty" }, "url": { "title": "URL", "type": "string", "description": "URL to the service", "format": "url", "icon": "DefaultProperty" }, "oncallUsers": { "type": "array", "title": "Current Oncalls", "items": { "type": "string", "format": "user" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "members": { "title": "Members", "target": "opsGenieUser", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: team selector: query: 'true' includeMembers: true port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieTeam"' properties: description: .description url: .links.web relations: members: if .__members != null then .__members | map(.user.id) else [] end ``` ## Schedule[​](#schedule "Direct link to Schedule") **Schedule blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieSchedule", "description": "This blueprint represents a OpsGenie schedule in our software catalog", "title": "OpsGenie Schedule", "icon": "OpsGenie", "schema": { "properties": { "timezone": { "title": "Timezone", "type": "string" }, "description": { "title": "Description", "type": "string" }, "users": { "title": "Users", "type": "array", "items": { "type": "string", "format": "user" } }, "startDate": { "title": "Start Date", "type": "string", "format": "date-time" }, "endDate": { "title": "End Date", "type": "string", "format": "date-time" }, "rotationType": { "type": "string", "title": "Rotation Type" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "ownerTeam": { "title": "Owner Team", "target": "opsGenieTeam", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: schedule selector: query: 'true' apiQueryParams: expand: rotation port: itemsToParse: .rotations entity: mappings: identifier: .id + "_" + .item.id title: .name + "_" + .item.name blueprint: '"opsGenieSchedule"' properties: timezone: .timezone description: .description startDate: .item.startDate endDate: .item.endDate rotationType: .item.type users: '[.item.participants[] | select(has("username")) | .username]' relations: ownerTeam: .ownerTeam.id ``` ## Service[​](#service "Direct link to Service") **Service blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieService", "description": "This blueprint represents an OpsGenie service in our software catalog", "title": "OpsGenie Service", "icon": "OpsGenie", "schema": { "properties": { "description": { "type": "string", "title": "Description", "icon": "DefaultProperty" }, "url": { "title": "URL", "type": "string", "description": "URL to the service", "format": "url", "icon": "DefaultProperty" }, "tags": { "type": "array", "items": { "type": "string" }, "title": "Tags", "icon": "DefaultProperty" } }, "required": [] }, "mirrorProperties": { "oncallUsers": { "title": "Current Oncalls", "path": "ownerTeam.oncallUsers" } }, "calculationProperties": { }, "aggregationProperties": { "numberOfOpenIncidents": { "title": "Number of open incidents", "type": "number", "target": "opsGenieIncident", "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "open" } ] }, "calculationSpec": { "calculationBy": "entities", "func": "count" } } }, "relations": { "ownerTeam": { "title": "Owner Team", "target": "opsGenieTeam", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: service selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"opsGenieService"' properties: description: .description url: .links.web tags: .tags relations: ownerTeam: .teamId ``` ## Incident[​](#incident "Direct link to Incident") **Incident blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieIncident", "description": "This blueprint represents an OpsGenie incident in our software catalog", "title": "OpsGenie Incident", "icon": "OpsGenie", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "status": { "type": "string", "title": "Status", "enum": [ "closed", "open", "resolved" ], "enumColors": { "closed": "blue", "open": "red", "resolved": "green" }, "description": "The status of the incident" }, "url": { "type": "string", "format": "url", "title": "URL" }, "tags": { "type": "array", "items": { "type": "string" }, "title": "Tags" }, "priority": { "type": "string", "title": "Priority" }, "createdAt": { "title": "Create At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "services": { "title": "Impacted Services", "target": "opsGenieService", "many": true, "required": false }, "respondingTeam": { "title": "Responding Team", "target": "opsGenieTeam", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: incident selector: query: 'true' apiQueryParams: status: open port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieIncident"' properties: status: .status responders: .responders priority: .priority tags: .tags url: .links.web createdAt: .createdAt updatedAt: .updatedAt description: .description relations: services: .impactedServices respondingTeam: .responders | [.[] | select(.type == "team") | .id] ``` ## Alert[​](#alert "Direct link to Alert") **Alert blueprint (click to expand)** Create in Port ``` { "identifier": "opsGenieAlert", "description": "This blueprint represents an OpsGenie alert in our software catalog", "title": "OpsGenie Alert", "icon": "OpsGenie", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "status": { "type": "string", "title": "Status", "enum": [ "closed", "open" ], "enumColors": { "closed": "green", "open": "red" }, "description": "The status of the alert" }, "acknowledged": { "type": "boolean", "title": "Acknowledged" }, "tags": { "type": "array", "items": { "type": "string" }, "title": "Tags" }, "integration": { "type": "string", "title": "Integration", "description": "The name of the Integration" }, "priority": { "type": "string", "title": "Priority" }, "sourceName": { "type": "string", "title": "Source Name", "description": "Alert source name" }, "createdBy": { "title": "Created By", "type": "string", "format": "user" }, "createdAt": { "title": "Create At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" }, "count": { "title": "Count", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "relatedIncident": { "title": "Related Incident", "target": "opsGenieIncident", "required": false, "many": false }, "respondingTeam": { "title": "Responding Team", "target": "opsGenieTeam", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: alert selector: query: 'true' apiQueryParams: status: open port: entity: mappings: identifier: .id title: .message blueprint: '"opsGenieAlert"' properties: status: .status acknowledged: .acknowledged responders: .responders priority: .priority sourceName: .source tags: .tags count: .count createdBy: .owner createdAt: .createdAt updatedAt: .updatedAt description: .description integration: .integration.name relations: relatedIncident: 'if (.alias | contains("_")) then (.alias | split("_")[0]) else null end' respondingTeam: .responders | [.[] | select(.type == "team") | .id] ``` filter alerts and incidents The integration provides an option to filter the data that is retrieved from the OpsGenie API using the following attributes: 1. `createdAt`: The date and time the alert or incident was created 2. `lastOccurredAt`: The date and time the alert or incident was last occurred 3. `snoozedUntil`: The date and time the alert or incident was snoozed until 4. `priority`: The priority of the alert or incident. Accepts values such as `P1`, `P2`, `P3`, `P4` and `P5` 5. `status`: The status of the alert or incident. Accepts values such as `open`, `closed` and `resolved` 6. `isSeen`: Whether the alert or incident has been seen. Accepts a boolean `true` or `false` 7. `acknowledged`: Whether the alert or incident has been acknowledged. Accepts a boolean `true` or `false` 8. `snoozed`: Whether the alert or incident has been snoozed. Accepts a boolean `true` or `false` 9. `owner`: The owner of the alert or incident. Accepts an OpsGenie username 10. `teams`: The teams associated with the alert or incident 11. `acknowledgedBy`: The user who acknowledged the alert or incident 12. `closedBy`: The user who closed the alert or incident 13. `message`: The message of the alert or incident These attributes can be enabled using the path: `selector.apiQueryParams`. By default, the integration fetches `open` alerts and incidents. ## Current On-call[​](#current-on-call "Direct link to Current On-call") To bring the current on-call users, update your configuration mapping to populate the `OpsGenieTeam` blueprint with team and on-call data. This will enable you to view on-call information at the service level: **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: schedule-oncall selector: query: 'true' port: entity: mappings: identifier: .ownerTeam.id title: .ownerTeam.name blueprint: '"opsGenieTeam"' properties: oncallUsers: .__currentOncalls.onCallRecipients ``` --- # PagerDuty ![PagerDuty icon](/img/guides/icons/PagerDuty.svg)![PagerDuty icon](/img/guides/icons/PagerDuty.svg) Loading version... Port's PagerDuty integration allows you to model PagerDuty resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[User](https://developer.pagerduty.com/api-reference/c96e889522dd6-list-users)[Schedule](https://developer.pagerduty.com/api-reference/846ecf84402bb-list-schedules)[Oncall](https://developer.pagerduty.com/api-reference/3a6b910f11050-list-all-of-the-on-calls)[Service](https://developer.pagerduty.com/api-reference/e960cca205c0f-list-services)[Incident](https://developer.pagerduty.com/api-reference/9d0b4b12e36f9-list-incidents)[Escalation Policy](https://developer.pagerduty.com/api-reference/51b21014a4f5a-list-escalation-policies) ## Core support [Live eventsSee supported resources](#set-up-live-events) Ingesting additional resources The resources listed above are just a subset of what the PagerDuty integration supports. You can ingest additional PagerDuty resources if they have a `GET List ` endpoint in the [PagerDuty API documentation](https://developer.pagerduty.com/api-reference/e65c5833eeb07-pager-duty-api). This means, resources such as teams, audit records, business services, extensions, incident workflows, status dashboards, vendor etc can be ingested into Port. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port with OAuth2Install Hosted by Port with OAuth * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Fast OAuth setup - no API token required * ✧Great for getting started, not recommended for production environments Hosted by Port with custom settings * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: services selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyService"' properties: status: .status url: .html_url oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email secondaryOncall: .__oncall_user | sort_by(.escalation_level) | .[1].user.email escalationLevels: .__oncall_user | map(.escalation_level) | unique | length meanSecondsToResolve: .__analytics.mean_seconds_to_resolve meanSecondsToFirstAck: .__analytics.mean_seconds_to_first_ack meanSecondsToEngage: .__analytics.mean_seconds_to_engage - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - resolved port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .html_url urgency: .urgency escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description triggered_by: .first_trigger_log_entry.agent.summary relations: pagerdutyService: .service.id service: combinator: '"and"' rules: - property: '"pagerdutyServiceId"' operator: '"="' value: .service.id - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - triggered - acknowledged port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .html_url urgency: .urgency escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description resolvedAt: .resolved_at recoveryTime: |- (.created_at as $createdAt | .resolved_at as $resolvedAt | if $resolvedAt == null then null else ( ($resolvedAt | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($createdAt | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) ) / 60 | floor end) triggered_by: .first_trigger_log_entry.agent.summary relations: pagerdutyService: .service.id incident_port_assignee: combinator: '"and"' rules: - property: '"pagerduty_user_id"' operator: '"in"' value: .assignments | map(.assignee.id) incident_pagerduty_assignee: .assignments | map(.assignee.id) - kind: schedules selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutySchedule"' properties: url: .html_url timezone: .time_zone description: .description users: '[.users[] | select(has("__email")) | .__email]' - kind: oncalls selector: query: 'true' apiQueryParams: include: - users port: entity: mappings: identifier: .user.id + "-" + .schedule.id + "-" + .start title: .user.name blueprint: '"pagerdutyOncall"' properties: startDate: .start endDate: .end url: .schedule.html_url relations: pagerdutySchedule: .schedule.id pagerdutyEscalationPolicy: .escalation_policy.id pagerduty_user: .user.id port_user: combinator: '"and"' rules: - property: '"$identifier"' operator: '"="' value: .user.email - kind: escalation_policies selector: query: 'true' attachOncallUsers: true port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyEscalationPolicy"' properties: url: .html_url summary: .summary primaryOncall: .__oncall_users | sort_by(.escalation_level) | .[0].user.email escalationRules: .escalation_rules - kind: users selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyUser"' properties: url: .html_url time_zone: .time_zone email: .email description: .description role: .role job_title: .job_title teams: .teams contact_methods: .contact_methods - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - resolved port: entity: mappings: identifier: .id | tostring blueprint: '"pagerdutyIncident"' relations: original_alert: .first_trigger_log_entry.channel.details.id extrakey: if .kind == "Incident" then .children.edges[].node.identifier else null end additionalField: .some.new.field.value - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - triggered - acknowledged inducer: - assignees - first_trigger_log_entries port: entity: mappings: identifier: .id | tostring blueprint: '"pagerdutyIncident"' relations: original_alert: .first_trigger_log_entry.channel.details.id extrakey: if .kind == "Incident" then .children.edges[].node.identifier else null end ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. ServiceIncidentScheduleOncallEscalation PolicyUser Default mappingYAML ``` - kind: services selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyService"' properties: status: .status url: .html_url oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email secondaryOncall: .__oncall_user | sort_by(.escalation_level) | .[1].user.email escalationLevels: .__oncall_user | map(.escalation_level) | unique | length meanSecondsToResolve: .__analytics.mean_seconds_to_resolve meanSecondsToFirstAck: .__analytics.mean_seconds_to_first_ack meanSecondsToEngage: .__analytics.mean_seconds_to_engage ``` Sample payloadJSON ``` { "id": "PGAAJBE", "name": "My Test Service", "description": "For testing", "created_at": "2023-08-03T16:53:48+03:00", "updated_at": "2023-08-03T16:53:48+03:00", "status": "active", "teams": [], "alert_creation": "create_alerts_and_incidents", "addons": [], "scheduled_actions": [], "support_hours": "None", "last_incident_timestamp": "None", "escalation_policy": { "id": "P7LVMYP", "type": "escalation_policy_reference", "summary": "Test Escalation Policy", "self": "https://api.pagerduty.com/escalation_policies/P7LVMYP", "html_url": "https://getport-io.pagerduty.com/escalation_policies/P7LVMYP" }, "incident_urgency_rule": { "type": "constant", "urgency": "high" }, "acknowledgement_timeout": "None", "auto_resolve_timeout": "None", "integrations": [], "type": "service", "summary": "My Test Service", "self": "https://api.pagerduty.com/services/PGAAJBE", "html_url": "https://getport-io.pagerduty.com/service-directory/PGAAJBE", "__oncall_user": [ { "escalation_policy": { "id": "P7LVMYP", "type": "escalation_policy_reference", "summary": "Test Escalation Policy", "self": "https://api.pagerduty.com/escalation_policies/P7LVMYP", "html_url": "https://getport-io.pagerduty.com/escalation_policies/P7LVMYP" }, "escalation_level": 1, "schedule": { "id": "PWAXLIH", "type": "schedule_reference", "summary": "Port Test Service - Weekly Rotation", "self": "https://api.pagerduty.com/schedules/PWAXLIH", "html_url": "https://getport-io.pagerduty.com/schedules/PWAXLIH" }, "user": { "name": "demo", "email": "devops-port@pager-demo.com", "time_zone": "Asia/Jerusalem", "color": "teal", "avatar_url": "https://secure.gravatar.com/avatar/5cc831a4e778f54460efc4cd20d13acd.png?d=mm&r=PG", "billed": true, "role": "admin", "description": "None", "invitation_sent": true, "job_title": "None", "teams": [], "contact_methods": [ { "id": "POKPUFD", "type": "email_contact_method_reference", "summary": "Default", "self": "https://api.pagerduty.com/users/PYIEKLY/contact_methods/POKPUFD", "html_url": "None" } ], "notification_rules": [ { "id": "P9NWEKF", "type": "assignment_notification_rule_reference", "summary": "0 minutes: channel POKPUFD", "self": "https://api.pagerduty.com/users/PYIEKLY/notification_rules/P9NWEKF", "html_url": "None" }, { "id": "PPJHFA5", "type": "assignment_notification_rule_reference", "summary": "0 minutes: channel POKPUFD", "self": "https://api.pagerduty.com/users/PYIEKLY/notification_rules/PPJHFA5", "html_url": "None" } ], "id": "PYIEKLY", "type": "user", "summary": "demo", "self": "https://api.pagerduty.com/users/PYIEKLY", "html_url": "https://getport-io.pagerduty.com/users/PYIEKLY" }, "start": "2023-10-17T15:57:50Z", "end": "2024-02-13T22:16:48Z" } ] } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | ----------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | API query parameters to include when fetching services. | | `serviceAnalytics` | `boolean` | If set to true, will ingest service analytics data to Port Default: `true`. | | `analyticsMonthsPeriod` | `integer` | Number of months to consider for the service analytics date range. Default: `3`. | | `includeCustomFields` | `boolean` | If set to true, will fetch and attach custom field values for each service Default: `false`. | `apiQueryParams` parameters | Parameter | Type | Description | | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `include` | `array` | Additional details to include in the response. Allowed values: `escalation_policies`, `teams`, `integrations`, `auto_pause_notifications_parameters`. | | `sort_by` | `string` | Sort services by name. Allowed values: `name`, `name:asc`, `name:desc`. | | `team_ids` | `array` | Filter services by team IDs. | | `time_zone` | `string` | Time zone to use for the response. Defaults to the account's time zone. e.g. 'America/New\_York', 'Europe/London', 'UTC'. | Default mappingYAML ``` - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - resolved port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .html_url urgency: .urgency escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description triggered_by: .first_trigger_log_entry.agent.summary relations: pagerdutyService: .service.id service: combinator: '"and"' rules: - property: '"pagerdutyServiceId"' operator: '"="' value: .service.id - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - triggered - acknowledged port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .html_url urgency: .urgency escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description resolvedAt: .resolved_at recoveryTime: |- (.created_at as $createdAt | .resolved_at as $resolvedAt | if $resolvedAt == null then null else ( ($resolvedAt | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) - ($createdAt | strptime("%Y-%m-%dT%H:%M:%SZ") | mktime) ) / 60 | floor end) triggered_by: .first_trigger_log_entry.agent.summary relations: pagerdutyService: .service.id incident_port_assignee: combinator: '"and"' rules: - property: '"pagerduty_user_id"' operator: '"in"' value: .assignments | map(.assignee.id) incident_pagerduty_assignee: .assignments | map(.assignee.id) - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - resolved port: entity: mappings: identifier: .id | tostring blueprint: '"pagerdutyIncident"' relations: original_alert: .first_trigger_log_entry.channel.details.id extrakey: if .kind == "Incident" then .children.edges[].node.identifier else null end additionalField: .some.new.field.value - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - triggered - acknowledged inducer: - assignees - first_trigger_log_entries port: entity: mappings: identifier: .id | tostring blueprint: '"pagerdutyIncident"' relations: original_alert: .first_trigger_log_entry.channel.details.id extrakey: if .kind == "Incident" then .children.edges[].node.identifier else null end ``` Sample payloadJSON ``` { "incident_number": 2, "title": "Example Incident", "description": "Example Incident", "created_at": "2023-05-15T13:59:45Z", "updated_at": "2023-05-15T13:59:45Z", "status": "triggered", "incident_key": "89809d37f4344d36a90c0a192c20c617", "service": { "id": "PWJAGSD", "type": "service_reference", "summary": "Port Test Service", "self": "https://api.pagerduty.com/services/PWJAGSD", "html_url": "https://getport-io.pagerduty.com/service-directory/PWJAGSD" }, "assignments": [ { "at": "2023-05-15T13:59:45Z", "assignee": { "id": "PJCRRLH", "type": "user_reference", "summary": "Username", "self": "https://api.pagerduty.com/users/PJCRRLH", "html_url": "https://getport-io.pagerduty.com/users/PJCRRLH" } } ], "assigned_via": "escalation_policy", "last_status_change_at": "2023-05-15T13:59:45Z", "resolved_at": null, "first_trigger_log_entry": { "id": "R5S5T07QR1SZRQFYB7SXEO2EKZ", "type": "trigger_log_entry_reference", "summary": "Triggered through the website.", "self": "https://api.pagerduty.com/log_entries/R5S5T07QR1SZRQFYB7SXEO2EKZ", "html_url": "https://getport-io.pagerduty.com/incidents/Q1P3AHC3KLGVAS/log_entries/R5S5T07QR1SZRQFYB7SXEO2EKZ" }, "alert_counts": { "all": 0, "triggered": 0, "resolved": 0 }, "is_mergeable": true, "escalation_policy": { "id": "P7LVMYP", "type": "escalation_policy_reference", "summary": "Test Escalation Policy", "self": "https://api.pagerduty.com/escalation_policies/P7LVMYP", "html_url": "https://getport-io.pagerduty.com/escalation_policies/P7LVMYP" }, "teams": [], "pending_actions": [], "acknowledgements": [], "basic_alert_grouping": null, "alert_grouping": null, "last_status_change_by": { "id": "PWJAGSD", "type": "service_reference", "summary": "Port Test Service", "self": "https://api.pagerduty.com/services/PWJAGSD", "html_url": "https://getport-io.pagerduty.com/service-directory/PWJAGSD" }, "urgency": "high", "id": "Q1P3AHC3KLGVAS", "type": "incident", "summary": "[#2] Example Incident", "self": "https://api.pagerduty.com/incidents/Q1P3AHC3KLGVAS", "html_url": "https://getport-io.pagerduty.com/incidents/Q1P3AHC3KLGVAS" } ``` Port entityJSON ``` { "identifier": "Q1P3AHC3KLGVAS", "title": "Test PagerDuty Incident", "blueprint": "pagerdutyIncident", "team": [], "properties": { "status": "triggered", "url": "https://your_account.pagerduty.com/incidents/Q01J2OS7YBWLNY", "details": "Test PagerDuty Incident", "urgency": "high", "responder": "Username", "escalation_policy": "Test Escalation Policy" }, "relations": { "microservice": "PWJAGSD" } } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | --------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | API query parameters to include when fetching incidents. | | `incidentAnalytics` | `boolean` | If set to true, will ingest incident analytics data to Port. Default: `false`. | | `includeCustomFields` | `boolean` | If set to true, will fetch and attach custom field values for each incident. Default: `false`. | `apiQueryParams` parameters | Parameter | Type | Description | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `date_range` | `string` | Filter incidents by date range. Use 'all' to return all incidents regardless of date. For a specific range, leave this unset and use the 'Since' and 'Until' fields instead. e.g. 'all'. Allowed values: `all`. | | `incident_key` | `string` | Filter incidents by incident key (deduplication key). | | `include` | `array` | Additional details to include in the response. Allowed values: `acknowledgers`, `agents`, `assignees`, `conference_bridge`, `escalation_policies`, `first_trigger_log_entries`, `priorities`, `services`, `teams`, `users`. | | `service_ids` | `array` | Filter incidents by service IDs. | | `since` | `string` | Start of the date range to filter incidents (ISO 8601 format). e.g. '2024-01-01T00:00:00Z'. | | `sort_by` | `string` | Sort incidents by a field and direction, e.g. 'created\_at:asc'. | | `statuses` | `array` | Filter incidents by status Allowed values: `triggered`, `acknowledged`, `resolved`. | | `team_ids` | `array` | Filter incidents by team IDs. | | `time_zone` | `string` | Time zone to use for the response. Defaults to the account's time zone. e.g. 'America/New\_York', 'Europe/London', 'UTC'. | | `until` | `string` | End of the date range to filter incidents (ISO 8601 format). e.g. '2024-12-31T23:59:59Z'. | | `urgencies` | `array` | Filter incidents by urgency Allowed values: `high`, `low`. | | `user_ids` | `array` | Filter incidents by user IDs. | Default mappingYAML ``` - kind: schedules selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutySchedule"' properties: url: .html_url timezone: .time_zone description: .description users: '[.users[] | select(has("__email")) | .__email]' ``` Sample payloadJSON ``` { "id": "PWAXLIH", "type": "schedule", "summary": "Port Test Service - Weekly Rotation", "self": "https://api.pagerduty.com/schedules/PWAXLIH", "html_url": "https://getport-io.pagerduty.com/schedules/PWAXLIH", "name": "Port Test Service - Weekly Rotation", "time_zone": "Asia/Jerusalem", "description": "This is the weekly on call schedule for Port Test Service associated with your first escalation policy.", "users": [ { "id": "PJCRRLH", "type": "user_reference", "summary": "Adam", "self": "https://api.pagerduty.com/users/PJCRRLH", "html_url": "https://getport-io.pagerduty.com/users/PJCRRLH" }, { "id": "P4K4DLP", "type": "user_reference", "summary": "Alice", "self": "https://api.pagerduty.com/users/P4K4DLP", "html_url": "https://getport-io.pagerduty.com/users/P4K4DLP" }, { "id": "HDW63E2", "type": "user_reference", "summary": "Doe", "self": "https://api.pagerduty.com/users/HDW63E2", "html_url": "https://getport-io.pagerduty.com/users/HDW63E2" }, { "id": "PRGAUI4", "type": "user_reference", "summary": "Pages", "self": null, "html_url": "https://getport-io.pagerduty.com/users/PRGAUI4", "deleted_at": "2023-10-17T18:58:07+03:00" }, { "id": "PYIEKLY", "type": "user_reference", "summary": "Demo", "self": "https://api.pagerduty.com/users/PYIEKLY", "html_url": "https://getport-io.pagerduty.com/users/PYIEKLY" } ], "escalation_policies": [ { "id": "P7LVMYP", "type": "escalation_policy_reference", "summary": "Test Escalation Policy", "self": "https://api.pagerduty.com/escalation_policies/P7LVMYP", "html_url": "https://getport-io.pagerduty.com/escalation_policies/P7LVMYP" } ], "teams": [] } ``` Port entityJSON ``` { "identifier": "PWAXLIH", "title": "Port Test Service - Weekly Rotation", "icon": "pagerduty", "blueprint": "pagerdutySchedule", "team": [], "properties": { "url": "https://getport-io.pagerduty.com/schedules/PWAXLIH", "timezone": "Asia/Jerusalem", "description": "Asia/Jerusalem", "users": ["adam@getport-io.com", "alice@getport-io.com", "doe@getport-io.com", "demo@getport-io.com", "pages@getport-io.com"] }, "relations": {}, "createdAt": "2023-12-01T13:18:02.215Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-12-01T13:18:02.215Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | API query parameters to include when fetching schedules. | `apiQueryParams` parameters | Parameter | Type | Description | | ----------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- | | `include` | `array` | Additional details to include in the response. Allowed values: `schedule_layers`, `final_schedule`, `overrides_subschedule`. | | `until` | `integer` | Number of months ahead to end the schedule window at. | | `since` | `integer` | Number of months in the past to start the schedule window from. | | `time_zone` | `string` | Time zone to use for the schedule. Defaults to the account's time zone. e.g. 'America/New\_York', 'Europe/London', 'UTC'. | Default mappingYAML ``` - kind: oncalls selector: query: 'true' apiQueryParams: include: - users port: entity: mappings: identifier: .user.id + "-" + .schedule.id + "-" + .start title: .user.name blueprint: '"pagerdutyOncall"' properties: startDate: .start endDate: .end url: .schedule.html_url relations: pagerdutySchedule: .schedule.id pagerdutyEscalationPolicy: .escalation_policy.id pagerduty_user: .user.id port_user: combinator: '"and"' rules: - property: '"$identifier"' operator: '"="' value: .user.email ``` Sample payloadJSON ``` { "escalation_policy":{ "id":"P7LVMYP", "type":"escalation_policy_reference", "summary":"Test Escalation Policy", "self":"https://api.pagerduty.com/escalation_policies/P7LVMYP", "html_url":"https://getport-io.pagerduty.com/escalation_policies/P7LVMYP" }, "escalation_level":1, "schedule":{ "id":"PWAXLIH", "type":"schedule_reference", "summary":"Port Test Service - Weekly Rotation", "self":"https://api.pagerduty.com/schedules/PWAXLIH", "html_url":"https://getport-io.pagerduty.com/schedules/PWAXLIH" }, "user":{ "name":"John Doe", "email":"johndoe@domain.io", "time_zone":"Asia/Jerusalem", "color":"red", "avatar_url":"https://secure.gravatar.com/avatar/149cf38119ee25af9b8b3a68d06f39e3.png?d=mm&r=PG", "billed":true, "role":"user", "description":null, "invitation_sent":false, "job_title":null, "teams":[ ], "contact_methods":[ { "id":"PK3SHEX", "type":"email_contact_method_reference", "summary":"Default", "self":"https://api.pagerduty.com/users/HDW63E2/contact_methods/PK3SHEX", "html_url":null }, { "id":"PO3TNV8", "type":"phone_contact_method_reference", "summary":"Other", "self":"https://api.pagerduty.com/users/HDW63E2/contact_methods/PO3TNV8", "html_url":null }, { "id":"P7U59FI", "type":"sms_contact_method_reference", "summary":"Other", "self":"https://api.pagerduty.com/users/HDW63E2/contact_methods/P7U59FI", "html_url":null } ], "notification_rules":[ { "id":"PMTOCX1", "type":"assignment_notification_rule_reference", "summary":"0 minutes: channel PK3SHEX", "self":"https://api.pagerduty.com/users/HDW63E2/notification_rules/PMTOCX1", "html_url":null }, { "id":"P3HAND3", "type":"assignment_notification_rule_reference", "summary":"0 minutes: channel P7U59FI", "self":"https://api.pagerduty.com/users/HDW63E2/notification_rules/P3HAND3", "html_url":null } ], "id":"HDW63E2", "type":"user", "summary":"John Doe", "self":"https://api.pagerduty.com/users/HDW63E2", "html_url":"https://getport-io.pagerduty.com/users/HDW63E2" }, "start":"2024-02-25T00:00:00Z", "end":"2024-04-14T11:10:48Z" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | API query parameters to include when fetching on-call entries. | `apiQueryParams` parameters | Parameter | Type | Description | | --------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `include` | `array` | Additional details to include in the response. Default: `["users"]`. Allowed values: `users`, `escalation_policies`, `schedules`. | | `until` | `integer` | Number of months ahead to include on-call schedules. Defaults to 3 months from now. Default: `3`. | | `since` | `integer` | Number of months back to include on-call schedules. Defaults to the current month. Default: `0`. | Default mappingYAML ``` - kind: escalation_policies selector: query: 'true' attachOncallUsers: true port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyEscalationPolicy"' properties: url: .html_url summary: .summary primaryOncall: .__oncall_users | sort_by(.escalation_level) | .[0].user.email escalationRules: .escalation_rules ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | API query parameters to include when fetching escalation policies. | | `attachOncallUsers` | `boolean` | When set to true, it fetches the oncall data per escalation policy Default: `true`. | `apiQueryParams` parameters | Parameter | Type | Description | | ---------- | ------- | ---------------------------------------------------------------------------------------------- | | `include` | `array` | Additional details to include in the response. Allowed values: `services`, `teams`, `targets`. | | `team_ids` | `array` | Filter escalation policies by team IDs. | | `user_ids` | `array` | Filter escalation policies by user IDs. | Default mappingYAML ``` - kind: users selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyUser"' properties: url: .html_url time_zone: .time_zone email: .email description: .description role: .role job_title: .job_title teams: .teams contact_methods: .contact_methods ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 PagerDuty 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 PagerDuty 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If PagerDuty is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that PagerDuty can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Ingesting service analytics[​](#ingesting-service-analytics "Direct link to Ingesting service analytics") To enrich your PagerDuty service entities with analytics data, follow the steps below: 1. Update the service blueprint to include analytics properties. You can add any property that is returned from the [PagerDuty aggregated service analytics API](https://developer.pagerduty.com/api-reference/694e92fe4f943-get-aggregated-service-data) **Updated service blueprint (click to expand)** Create in Port ``` { "identifier":"pagerdutyService", "description":"This blueprint represents a PagerDuty service in our software catalog", "title":"PagerDuty Service", "icon":"pagerduty", "schema":{ "properties":{ "status":{ "title":"Status", "type":"string" }, "url":{ "title":"URL", "type":"string", "format":"url" }, "oncall":{ "title":"On Call", "type":"array", "items":{ "type":"string", "format":"user" } }, "meanSecondsToResolve":{ "title":"Mean Seconds to Resolve", "type":"number" }, "meanSecondsToFirstAck":{ "title":"Mean Seconds to First Acknowledge", "type":"number" }, "meanSecondsToEngage":{ "title":"Mean Seconds to Engage", "type":"number" }, "totalIncidentCount":{ "title":"Total Incident Count", "type":"number" }, "totalIncidentsAcknowledged":{ "title":"Total Incidents Acknowledged", "type":"number" }, "totalIncidentsAutoResolved":{ "title":"Total Incidents Auto Resolved", "type":"number" }, "totalIncidentsManualEscalated":{ "title":"Total Incident Manual Escalated", "type":"number" } }, "required":[] }, "mirrorProperties":{}, "calculationProperties":{}, "relations":{} } ``` 2. Add `serviceAnalytics` property to the integration `selector` key. When set to `true`, the integration will fetch data from the [PagerDuty aggregated service analytics API](https://developer.pagerduty.com/api-reference/694e92fe4f943-get-aggregated-service-data) and ingest it to Port. By default, this property is set to `true`. Also, by default, the integration aggregates the analytics over a period of 3 months. Use the `analyticsMonthsPeriod` filter to override this date range. The accepted values are positive number between 1 to 12. In the provided example below, we aggregate the analytics over the past 6 months. ``` resources: - kind: services selector: query: "true" serviceAnalytics: "true" analyticsMonthsPeriod: 6 port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyService"' properties: status: .status url: .html_url oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email secondaryOncall: .__oncall_user | sort_by(.escalation_level) | .[1].user.email ``` 3. Establish a mapping between the analytics properties and the service analytics data response. Following a convention, the aggregated result of the PagerDuty service analytics API is saved to the `__analytics` key and merged with the response of the service API. Consequently, users can access specific metrics such as the mean seconds to resolve by referencing `__analytics.mean_seconds_to_resolve`. ``` resources: - kind: services selector: query: "true" serviceAnalytics: "true" analyticsMonthsPeriod: 6 port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyService"' properties: status: .status url: .html_url oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email secondaryOncall: .__oncall_user | sort_by(.escalation_level) | .[1].user.email meanSecondsToResolve: .__analytics.mean_seconds_to_resolve ``` 4. Below is the complete integration configuration for enriching the service blueprint with analytics data. **Service analytics integration configuration (click to expand)** ``` resources: - kind: services selector: query: "true" serviceAnalytics: "true" analyticsMonthsPeriod: 6 port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyService"' properties: status: .status url: .html_url oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email secondaryOncall: .__oncall_user | sort_by(.escalation_level) | .[1].user.email meanSecondsToResolve: .__analytics.mean_seconds_to_resolve meanSecondsToFirstAck: .__analytics.mean_seconds_to_first_ack meanSecondsToEngage: .__analytics.mean_seconds_to_engage totalIncidentCount: .__analytics.total_incident_count totalIncidentsAcknowledged: .__analytics.total_incidents_acknowledged totalIncidentsAutoResolved: .__analytics.total_incidents_auto_resolved totalIncidentsManualEscalated: .__analytics.total_incidents_manual_escalated ``` ### Ingesting incident analytics[​](#ingesting-incident-analytics "Direct link to Ingesting incident analytics") To enrich your PagerDuty incident entities with analytics data, follow the steps below: 1. Update the incident blueprint to include an `analytics` property. **Updated incident blueprint (click to expand)** Create in Port ``` { "identifier": "pagerdutyIncident", "description": "This blueprint represents a PagerDuty incident in our software catalog", "title": "PagerDuty Incident", "icon": "pagerduty", "schema": { "properties": { "status": { "type": "string", "title": "Incident Status", "enum": [ "triggered", "annotated", "acknowledged", "reassigned", "escalated", "reopened", "resolved" ], "enumColors": { "triggered": "red", "annotated": "blue", "acknowledged": "yellow", "reassigned": "blue", "escalated": "yellow", "reopened": "red", "resolved": "green" } }, "url": { "type": "string", "format": "url", "title": "Incident URL" }, "urgency": { "title": "Incident Urgency", "type": "string", "enum": [ "high", "low" ], "enumColors": { "high": "red", "low": "green" } }, "priority": { "type": "string", "title": "Priority", "enum": [ "P1", "P2", "P3", "P4", "P5" ], "enumColors": { "P1": "red", "P2": "yellow", "P3": "blue", "P4": "lightGray", "P5": "darkGray" } }, "description": { "type": "string", "title": "Description" }, "assignees": { "title": "Assignees", "type": "array", "items": { "type": "string", "format": "user" } }, "escalation_policy": { "type": "string", "title": "Escalation Policy" }, "created_at": { "title": "Create At", "type": "string", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "format": "date-time" }, "analytics": { "title": "Analytics", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "pagerdutyService": { "title": "PagerDuty Service", "target": "pagerdutyService", "required": false, "many": true } } } ``` 2. Add `incidentAnalytics` property to the integration `selector` key. When set to `true`, the integration will fetch data from the [PagerDuty Analytics API](https://developer.pagerduty.com/api-reference/328d94baeaa0e-get-raw-data-single-incident) and ingest it to Port. By default, this property is set to `false`. ``` resources: - kind: incidents selector: query: "true" incidentAnalytics: "true" port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .self ``` 3. Establish a mapping between the `analytics` blueprint property and the analytics data response. ``` resources: - kind: incidents selector: query: 'true' include: ['assignees'] port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .self urgency: .urgency assignees: .assignments | map(.assignee.email) escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description analytics: .__analytics relations: pagerdutyService: .service.id ``` 4. Below is the complete integration configuration for enriching the incident blueprint with analytics data. **Incident analytics integration configuration (click to expand)** ``` resources: - kind: incidents selector: query: 'true' include: ['assignees'] port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .self urgency: .urgency assignees: .assignments | map(.assignee.email) escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description analytics: .__analytics relations: pagerdutyService: .service.id ``` ### Ingesting service custom fields[​](#ingesting-service-custom-fields "Direct link to Ingesting service custom fields") To enrich your PagerDuty service entities with [custom fields](https://support.pagerduty.com/main/docs/custom-fields-on-incidents) data, follow the steps below: 1. Update the service blueprint to include properties for the custom fields you want to ingest. When enabled, the integration fetches the [custom field values](https://docs.pagerduty.com/developer/api/reference/rest/services/get-service-custom-field-values) for each service and stores them under the `__custom_fields` key. Each custom field in the array has the following structure: ``` { "id": "FIELD_ID", "name": "my_custom_field", "display_name": "My Custom Field", "data_type": "string", "field_type": "single_value", "type": "field_value", "value": { "value": "field value here" } } ``` You can extract specific custom field values into individual blueprint properties using jq.
For example, if you have custom fields named `team` and `environment` in PagerDuty: **Updated service blueprint (click to expand)** Create in Port ``` { "identifier":"pagerdutyService", "description":"This blueprint represents a PagerDuty service in our software catalog", "title":"PagerDuty Service", "icon":"pagerduty", "schema":{ "properties":{ "status":{ "title":"Status", "type":"string" }, "url":{ "title":"URL", "type":"string", "format":"url" }, "oncall":{ "title":"On Call", "type":"array", "items":{ "type":"string", "format":"user" } }, "team":{ "title":"Team", "type":"string" }, "environment":{ "title":"Environment", "type":"string" } }, "required":[] }, "mirrorProperties":{}, "calculationProperties":{}, "relations":{} } ``` 2. Add `includeCustomFields` to the `selector` key and map specific custom field values using jq. When set to `true`, the integration fetches custom field values for each service. By default, this property is set to `false`. Use jq expressions to extract specific fields by their `name`, for example: `(.__custom_fields[] | select(.name == "team")).value`. ``` resources: - kind: services selector: query: "true" includeCustomFields: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyService"' properties: status: .status url: .html_url oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email secondaryOncall: .__oncall_user | sort_by(.escalation_level) | .[1].user.email team: (.__custom_fields[] | select(.name == "team")).value environment: (.__custom_fields[] | select(.name == "environment")).value ``` ### Ingesting incident custom fields[​](#ingesting-incident-custom-fields "Direct link to Ingesting incident custom fields") To enrich your PagerDuty incident entities with [custom fields](https://support.pagerduty.com/main/docs/custom-fields-on-incidents) data, follow the steps below: 1. Update the incident blueprint to include properties for the custom fields you want to ingest. The `__custom_fields` array has the same structure as described in the [service custom fields section](#ingesting-service-custom-fields) above. For example, if you have custom fields named `severity` and `affected_region` in PagerDuty: **Updated incident blueprint (click to expand)** Create in Port ``` { "identifier": "pagerdutyIncident", "description": "This blueprint represents a PagerDuty incident in our software catalog", "title": "PagerDuty Incident", "icon": "pagerduty", "schema": { "properties": { "status": { "type": "string", "title": "Incident Status", "enum": [ "triggered", "annotated", "acknowledged", "reassigned", "escalated", "reopened", "resolved" ], "enumColors": { "triggered": "red", "annotated": "blue", "acknowledged": "yellow", "reassigned": "blue", "escalated": "yellow", "reopened": "red", "resolved": "green" } }, "url": { "type": "string", "format": "url", "title": "Incident URL" }, "urgency": { "title": "Incident Urgency", "type": "string", "enum": [ "high", "low" ], "enumColors": { "high": "red", "low": "green" } }, "priority": { "type": "string", "title": "Priority", "enum": [ "P1", "P2", "P3", "P4", "P5" ], "enumColors": { "P1": "red", "P2": "yellow", "P3": "blue", "P4": "lightGray", "P5": "darkGray" } }, "description": { "type": "string", "title": "Description" }, "assignees": { "title": "Assignees", "type": "array", "items": { "type": "string", "format": "user" } }, "escalation_policy": { "type": "string", "title": "Escalation Policy" }, "created_at": { "title": "Create At", "type": "string", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "format": "date-time" }, "severity":{ "title": "Severity", "type": "string" }, "affectedRegion":{ "title": "Affected Region", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "pagerdutyService": { "title": "PagerDuty Service", "target": "pagerdutyService", "required": false, "many": true } } } ``` 2. Add `includeCustomFields` to the `selector` key and map specific custom field values using jq. When set to `true`, the integration fetches custom field values from the [PagerDuty custom fields API](https://docs.pagerduty.com/developer/api/reference/rest/incidents/get-incident-field-values) for each incident. By default, this property is set to `false`. Extract specific fields the same way as in the [service example](#ingesting-service-custom-fields) above. ``` resources: - kind: incidents selector: query: 'true' include: ['assignees'] includeCustomFields: 'true' port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .self urgency: .urgency assignees: .assignments | map(.assignee.email) escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description severity: (.__custom_fields[] | select(.name == "severity")).value affectedRegion: (.__custom_fields[] | select(.name == "affected_region")).value relations: pagerdutyService: .service.id ``` ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Additional examples of blueprints and the relevant integration configurations can be found on the pagerduty [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/examples/.md) ## Relevant guides[​](#relevant-guides "Direct link to Relevant guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=PagerDuty). ## Limitations[​](#limitations "Direct link to Limitations") * The PagerDuty API uses offset-based pagination, which has a limitation of retrieving a maximum of 10,000 resources. This may affect the integration's ability to sync data if you have more than 10,000 of a specific resource type (e.g., users, services, etc.). Refer to PagerDuty's [official documentation on pagination](https://developer.pagerduty.com/docs/pagination) for further details. ## Support[​](#support "Direct link to Support") For any questions or issues, contact us at [support.port.io](http://support.port.io/) or via our [Community Slack channel](https://port.io/community). --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ## User[​](#user "Direct link to User") **User blueprint (click to expand)** Create in Port ``` { "identifier": "pagerdutyUser", "description": "This blueprint represents a PagerDuty user in our software catalog", "title": "PagerDuty User", "icon": "pagerduty", "schema": { "properties": { "role": { "icon": "DefaultProperty", "title": "Role", "type": "string", "enum": [ "admin", "user", "observer", "limited_user", "owner", "read_only_user", "restricted_access", "read_only_limited_user" ] }, "url": { "icon": "DefaultProperty", "type": "string", "format": "url", "title": "User URL" }, "job_title": { "title": "Job Title", "icon": "DefaultProperty", "type": "string" }, "contact_methods": { "title": "Contact Methods", "icon": "DefaultProperty", "type": "array" }, "description": { "type": "string", "title": "Description" }, "teams": { "title": "Teams", "icon": "DefaultProperty", "type": "array" }, "time_zone": { "icon": "DefaultProperty", "type": "string", "title": "Time Zone" }, "email": { "type": "string", "title": "Email", "format": "user" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: users selector: query: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyUser"' properties: url: .html_url time_zone: .time_zone email: .email description: .description role: .role job_title: .job_title teams: .teams contact_methods: .contact_methods ``` ## Schedule[​](#schedule "Direct link to Schedule") **Schedule blueprint (click to expand)** Create in Port ``` { "identifier": "pagerdutySchedule", "description": "This blueprint represents a PagerDuty schedule in our software catalog", "title": "PagerDuty Schedule", "icon": "pagerduty", "schema": { "properties": { "url": { "title": "Schedule URL", "type": "string", "format": "url" }, "timezone": { "title": "Timezone", "type": "string" }, "description": { "title": "Description", "type": "string" }, "users": { "title": "Users", "type": "array", "items": { "type": "string", "format": "user" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: schedules selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutySchedule"' properties: url: .html_url timezone: .time_zone description: .description users: '[.users[] | select(has("__email")) | .__email]' ``` ## Oncall[​](#oncall "Direct link to Oncall") **Oncall blueprint (click to expand)** Create in Port ``` { "identifier": "pagerdutyOncall", "description": "This blueprint represents a PagerDuty oncall schedule in our software catalog", "title": "PagerDuty Oncall Schedule", "icon": "pagerduty", "schema": { "properties": { "url": { "title": "Oncall Schedule URL", "type": "string", "format": "url" }, "user": { "title": "User", "type": "string", "format": "user" }, "startDate": { "title": "Start Date", "type": "string", "format": "date-time" }, "endDate": { "title": "End Date", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "pagerdutySchedule": { "title": "PagerDuty Schedule", "target": "pagerdutySchedule", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: oncalls selector: query: 'true' apiQueryParams: include: ['users'] port: entity: mappings: identifier: .user.id + "-" + .schedule.id + "-" + .start title: .user.name blueprint: '"pagerdutyOncall"' properties: user: .user.email startDate: .start endDate: .end url: .schedule.html_url relations: pagerdutySchedule: .schedule.id ``` ## Service[​](#service "Direct link to Service") **Service blueprint (click to expand)** Create in Port ``` { "identifier": "pagerdutyService", "description": "This blueprint represents a PagerDuty service in our software catalog", "title": "PagerDuty Service", "icon": "pagerduty", "schema": { "properties": { "status": { "title": "Status", "type": "string", "enum": [ "active", "warning", "critical", "maintenance", "disabled" ], "enumColors": { "active": "green", "warning": "yellow", "critical": "red", "maintenance": "lightGray", "disabled": "darkGray" } }, "url": { "title": "URL", "type": "string", "format": "url" }, "oncall": { "title": "On Call", "type": "string", "format": "user" }, "secondaryOncall": { "title": "Secondary On Call", "type": "string", "format": "user" }, "escalationLevels": { "title": "Escalation Levels", "type": "number" }, "meanSecondsToResolve": { "title": "Mean Seconds to Resolve", "type": "number" }, "meanSecondsToFirstAck": { "title": "Mean Seconds to First Acknowledge", "type": "number" }, "meanSecondsToEngage": { "title": "Mean Seconds to Engage", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: services selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyService"' properties: status: .status url: .html_url oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email secondaryOncall: .__oncall_user | sort_by(.escalation_level) | .[1].user.email escalationLevels: .__oncall_user | map(.escalation_level) | unique | length meanSecondsToResolve: .__analytics.mean_seconds_to_resolve meanSecondsToFirstAck: .__analytics.mean_seconds_to_first_ack meanSecondsToEngage: .__analytics.mean_seconds_to_engage ``` **Integration configuration with custom fields (click to expand)** Custom fields When `includeCustomFields` is set to `true`, the integration fetches [custom field](https://support.pagerduty.com/main/docs/custom-fields-on-incidents) data for each service. The data is stored in the `.__custom_fields` key as an array of field objects. Use jq to extract specific field values by name, for example: `(.__custom_fields[] | select(.name == "my_field")).value`. By default, this property is set to `false`. ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: services selector: query: 'true' includeCustomFields: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyService"' properties: status: .status url: .html_url oncall: .__oncall_user | sort_by(.escalation_level) | .[0].user.email secondaryOncall: .__oncall_user | sort_by(.escalation_level) | .[1].user.email escalationLevels: .__oncall_user | map(.escalation_level) | unique | length meanSecondsToResolve: .__analytics.mean_seconds_to_resolve meanSecondsToFirstAck: .__analytics.mean_seconds_to_first_ack meanSecondsToEngage: .__analytics.mean_seconds_to_engage team: (.__custom_fields[] | select(.name == "team")).value environment: (.__custom_fields[] | select(.name == "environment")).value ``` ## Incident[​](#incident "Direct link to Incident") **Incident blueprint (click to expand)** Create in Port ``` { "identifier": "pagerdutyIncident", "description": "This blueprint represents a PagerDuty incident in our software catalog", "title": "PagerDuty Incident", "icon": "pagerduty", "schema": { "properties": { "status": { "type": "string", "title": "Incident Status", "enum": [ "triggered", "annotated", "acknowledged", "reassigned", "escalated", "reopened", "resolved" ], "enumColors": { "triggered": "red", "annotated": "blue", "acknowledged": "yellow", "reassigned": "blue", "escalated": "yellow", "reopened": "red", "resolved": "green" } }, "url": { "type": "string", "format": "url", "title": "Incident URL" }, "urgency": { "title": "Incident Urgency", "type": "string", "enum": [ "high", "low" ], "enumColors": { "high": "red", "low": "green" } }, "priority": { "type": "string", "title": "Priority", "enum": [ "P1", "P2", "P3", "P4", "P5" ], "enumColors": { "P1": "red", "P2": "yellow", "P3": "blue", "P4": "lightGray", "P5": "darkGray" } }, "description": { "type": "string", "title": "Description" }, "assignees": { "title": "Assignees", "type": "array", "items": { "type": "string", "format": "user" } }, "escalation_policy": { "type": "string", "title": "Escalation Policy" }, "created_at": { "title": "Create At", "type": "string", "format": "date-time" }, "updated_at": { "title": "Updated At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "pagerdutyService": { "title": "PagerDuty Service", "target": "pagerdutyService", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** Double mapping for incidents The mapping for incidents is divided into two types: **Ongoing Incidents**: This mapping is used for incidents with a status of `triggered` or `acknowledged`. **Resolved Incidents**: This mapping is used for incidents that have been `resolved`. The separation is necessary to prevent overwriting the previously ingested `assignee` data. When an incident is resolved, the `assignee` field in the Incident API response is set to `null`, which could otherwise overwrite the existing assignee information. ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - triggered - acknowledged port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .html_url urgency: .urgency assignees: .assignments | map(.assignee.email) escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description triggered_by: .first_trigger_log_entry.agent.summary relations: pagerdutyService: .service.id - kind: incidents selector: query: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - resolved port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .html_url urgency: .urgency escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description triggered_by: .first_trigger_log_entry.agent.summary relations: pagerdutyService: .service.id ``` **Integration configuration with custom fields (click to expand)** Custom fields When `includeCustomFields` is set to `true`, the integration fetches [custom field](https://support.pagerduty.com/main/docs/custom-fields-on-incidents) data for each incident. The data is stored in the `.__custom_fields` key as an array of field objects. Use jq to extract specific field values by name, for example: `(.__custom_fields[] | select(.name == "my_field")).value`. By default, this property is set to `false`. ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: incidents selector: query: 'true' includeCustomFields: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - triggered - acknowledged port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .html_url urgency: .urgency assignees: .assignments | map(.assignee.email) escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description triggered_by: .first_trigger_log_entry.agent.summary severity: (.__custom_fields[] | select(.name == "severity")).value affectedRegion: (.__custom_fields[] | select(.name == "affected_region")).value relations: pagerdutyService: .service.id - kind: incidents selector: query: 'true' includeCustomFields: 'true' apiQueryParams: include: - assignees - first_trigger_log_entries statuses: - resolved port: entity: mappings: identifier: .id | tostring title: .title blueprint: '"pagerdutyIncident"' properties: status: .status url: .html_url urgency: .urgency escalation_policy: .escalation_policy.summary created_at: .created_at updated_at: .updated_at priority: if .priority != null then .priority.summary else null end description: .description triggered_by: .first_trigger_log_entry.agent.summary severity: (.__custom_fields[] | select(.name == "severity")).value affectedRegion: (.__custom_fields[] | select(.name == "affected_region")).value relations: pagerdutyService: .service.id ``` ## Escalation Policy[​](#escalation-policy "Direct link to Escalation Policy") **Escalation Policy blueprint (click to expand)** Create in Port ``` { "identifier": "pagerdutyEscalationPolicy", "description": "This blueprint represents a PagerDuty escalation policy in our software catalog", "title": "PagerDuty Escalation Policy", "icon": "pagerduty", "schema": { "properties": { "url": { "title": "URL", "type": "string", "format": "url" }, "summary": { "title": "Summary", "type": "string" }, "primaryOncall": { "title": "Primary Oncall", "type": "string", "format": "user" }, "escalationRules": { "title": "Escalation Rules", "type": "array", "items": { "type": "object" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** Attach oncall users When `attachOncallUsers` is set to `true`, it fetches the oncall data per escalation policy. To disable this feature, set the value to `false`. ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: escalation_policies selector: query: 'true' attachOncallUsers: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"pagerdutyEscalationPolicy"' properties: url: .html_url description: .summary primaryOncall: .__oncall_users | sort_by(.escalation_level) | .[0].user.email escalationRules: .escalation_rules ``` ## Slack notifications[​](#slack-notifications "Direct link to Slack notifications") Using Port's automation capabilities, you can set up real-time Slack notifications when incidents are created or updated in PagerDuty. **Automation for New Incidents (click to expand)** Create in Port ``` { "identifier": "pagerduty_new_incident_notification", "title": "Notify Slack on New PagerDuty Incident", "icon": "Slack", "description": "Sends a Slack notification when a new incident is created in PagerDuty", "trigger": { "type": "automation", "event": { "type": "ENTITY_CREATED", "blueprintIdentifier": "pagerdutyIncident" }, "condition": { "type": "JQ", "expressions": [ ".diff.after.properties.status == \"triggered\"" ], "combinator": "and" } }, "invocationMethod": { "type": "WEBHOOK", "url": "YOUR_SLACK_WEBHOOK_URL", "agent": false, "synchronized": true, "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "text": "🚨 New PagerDuty Incident\n\n*Incident:* {{ .event.diff.after.title }}\n*Status:* {{ .event.diff.after.properties.status }}\n*Urgency:* {{ .event.diff.after.properties.urgency }}\n*Service:* {{ .event.diff.after.relations.pagerdutyService }}\n\n<{{ .event.diff.after.properties.url }}|View in PagerDuty> | " } }, "publish": true } ``` **Automation for Incident Status Changes (click to expand)** Create in Port ``` { "identifier": "pagerduty_incident_status_change", "title": "Notify Slack on PagerDuty Incident Status Change", "icon": "Slack", "description": "Sends a Slack notification when a PagerDuty incident status changes", "trigger": { "type": "automation", "event": { "type": "ENTITY_UPDATED", "blueprintIdentifier": "pagerdutyIncident" }, "condition": { "type": "JQ", "expressions": [ ".diff.before.properties.status != .diff.after.properties.status" ], "combinator": "and" } }, "invocationMethod": { "type": "WEBHOOK", "url": "YOUR_SLACK_WEBHOOK_URL", "agent": false, "synchronized": true, "method": "POST", "headers": {}, "body": { "blocks": [ { "type": "header", "text": { "type": "plain_text", "text": "🔄 PagerDuty Incident Status Change", "emoji": true } }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "*Incident:*\n{{ .event.diff.after.title }}" }, { "type": "mrkdwn", "text": "*Status:*\n{{ if eq .event.diff.after.properties.status \"resolved\" }}✅ Resolved{{ else if eq .event.diff.after.properties.status \"acknowledged\" }}👀 Acknowledged{{ else }}🚨 {{ .event.diff.after.properties.status }}{{ end }}" } ] }, { "type": "section", "fields": [ { "type": "mrkdwn", "text": "*Previous Status:*\n{{ .event.diff.before.properties.status }}" }, { "type": "mrkdwn", "text": "*Updated:*\n{{ .event.diff.after.properties.updated_at }}" } ] } ] } }, "publish": true } ``` Slack webhook URL Replace `YOUR_SLACK_WEBHOOK_URL` with your actual Slack incoming webhook URL. For information on creating Slack webhooks, see the [Slack API documentation](https://api.slack.com/messaging/webhooks). These automations allow your team to receive immediate notifications in Slack when incidents are created or change status, making incident response faster and more efficient. --- # Rootly Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's Rootly integration allows you to ingest Rootly incident management resources into your software catalog using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. After installing this integration, you would be able to visualize your Rootly incidents, services, teams, and on-call schedules using Port's dashboards. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The Rootly integration can ingest the following resources into Port. It is possible to reference any field that appears in the API responses in the mapping configuration. For detailed API documentation, see the [Rootly API documentation](https://docs.rootly.com/api-reference/overview). * **Incidents** - Incident records and their details from [`/incidents`](https://docs.rootly.com/api-reference/incidents/list-incidents). * **Services** - Service catalog entries from [`/services`](https://docs.rootly.com/api-reference/services/list-services). * **Teams** - Team information from [`/teams`](https://docs.rootly.com/api-reference/teams/list-teams). * **Users** - User accounts from [`/users`](https://docs.rootly.com/api-reference/users/list-users). * **Environments** - Environment configurations from [`/environments`](https://docs.rootly.com/api-reference/environments/list-environments). * **Severities** - Incident severity levels from [`/severities`](https://docs.rootly.com/api-reference/severities/list-severities). * **Functionalities** - Functionality catalog from [`/functionalities`](https://docs.rootly.com/api-reference/functionalities/list-functionalities). ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, you need: * A Rootly API key with appropriate permissions to access incident and catalog data. * The API key should have read access to incidents, services, teams, and other resources you want to sync. ### Create a Rootly API key[​](#create-a-rootly-api-key "Direct link to Create a Rootly API key") 1. Log in to your Rootly account. 2. Click on your **Organization dropdown** in the top navigation. 3. Select **Organization Settings**. 4. Navigate to **API Keys** in the left sidebar. 5. Click **Generate New API Key**. 6. Select the appropriate API key type: * **Global API Key** - Full access to all entities (recommended for integration). * **Team API Key** - Limited to specific team resources. * **Personal API Key** - Inherits your user permissions. 7. Provide a name for your API key (e.g., "Port Integration"). 8. Click **Generate**. 9. Copy the API key immediately and save it securely. API key security Store your API key securely and never share it. The key provides access to your Rootly data. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. Configure: * **Identifier**: `my-ocean-rootly-integration`. * **Base URL**: `https://api.rootly.com/v1`. * **Authentication type**: `bearer_token`. * **Token**: your Rootly API key. 3. Expand **Advanced configuration** and set: * **Pagination type**: `page`. 4. Click **Save & install**. Continue with the mappings After the integration is installed, continue to the **Configuration** section on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_ROOTLY_API_KEY`. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-rootly-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set scheduledResyncInterval=120 \ --set integration.identifier="my-ocean-rootly-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://api.rootly.com/v1" \ --set integration.config.authType="bearer_token" \ --set integration.config.paginationType="page" \ --set integration.config.apiToken="YOUR_ROOTLY_API_KEY" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters This table summarizes the available parameters for the installation. | Parameter | Description | Example | Required | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | -------- | | `port.clientId` | Your Port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.baseUrl` | Your Port API URL - `https://api.port.io` for EU, `https://api.us.port.io` for US | | ✅ | | `integration.config.baseUrl` | The base URL of the Rootly API instance | | ✅ | | `integration.config.authType` | The authentication type for the API (use `bearer_token` for Rootly) | bearer\_token | ✅ | | `integration.config.apiToken` | Your Rootly API key | | ✅ | | `integration.config.paginationType` | How your API handles pagination (offset, page, cursor, or none) | page | ❌ | | `integration.eventListener.type` | The event listener type. Read more about [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) | POLLING | ✅ | | `integration.type` | The integration type (must be `custom` for the no-code Ocean custom integration). | custom | ✅ | | `integration.identifier` | Unique identifier for the integration instance | my-ocean-rootly-integration | ✅ | | `scheduledResyncInterval` | The number of minutes between each resync. When not set the integration will resync for each event listener resync event. Read more about [scheduledResyncInterval](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) | 120 | ❌ | | `initializePortResources` | Default true, When set to true the integration will create default blueprints and the port App config Mapping. | true | ❌ | | `sendRawDataExamples` | Enable sending raw data examples from the third party API to port for testing and managing the integration mapping. Default is true | true | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_ROOTLY_API_KEY`. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://api.rootly.com/v1" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="page" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="YOUR_ROOTLY_API_KEY" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before the integration can sync data, you need to create the required blueprints in Port. These blueprints define the data model for your Rootly resources. **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on the `+ Blueprint` button. 3. Copy and paste each blueprint JSON from the sections below. **Rootly Incident Blueprint (click to expand)** Incident records and their details: Create in Port ``` { "identifier": "rootly-incident", "title": "Rootly Incident", "icon": "Rootly", "schema": { "properties": { "title": { "title": "Title", "type": "string" }, "status": { "title": "Status", "type": "string" }, "severity": { "title": "Severity", "type": "string" }, "started_at": { "title": "Started At", "type": "string", "format": "date-time" }, "resolved_at": { "title": "Resolved At", "type": "string", "format": "date-time" }, "url": { "title": "Incident URL", "type": "string", "format": "url" }, "summary": { "title": "Summary", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Rootly Service Blueprint (click to expand)** Service catalog entries: Create in Port ``` { "identifier": "rootly-service", "title": "Rootly Service", "icon": "Rootly", "schema": { "properties": { "name": { "title": "Name", "type": "string" }, "description": { "title": "Description", "type": "string" }, "slug": { "title": "Slug", "type": "string" }, "color": { "title": "Color", "type": "string" }, "notify_emails": { "title": "Notify Emails", "type": "array", "items": { "type": "string" } } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Rootly Team Blueprint (click to expand)** Team information: Create in Port ``` { "identifier": "rootly-team", "title": "Rootly Team", "icon": "Team", "schema": { "properties": { "name": { "title": "Name", "type": "string" }, "description": { "title": "Description", "type": "string" }, "slug": { "title": "Slug", "type": "string" }, "color": { "title": "Color", "type": "string" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Rootly User Blueprint (click to expand)** User accounts: Create in Port ``` { "identifier": "rootly-user", "title": "Rootly User", "icon": "User", "schema": { "properties": { "email": { "title": "Email", "type": "string", "format": "email" }, "full_name": { "title": "Full Name", "type": "string" }, "role": { "title": "Role", "type": "string" }, "accepted_invite_at": { "title": "Accepted Invite At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` 4. Click `Save` to save the blueprint. ## Configuration[​](#configuration "Direct link to Configuration") After installation, define which endpoints to sync in your integration configuration. Each resource maps an API endpoint to Port entities using [JQ expressions](https://stedolan.github.io/jq/manual/) to transform the data. **Key mapping components:** * **`kind`**: The API endpoint path (combined with your base URL). * **`selector.query`**: JQ filter to include/exclude entities (use `'true'` to sync all). * **`selector.data_path`**: JQ expression pointing to the array of items in the response. * **`port.entity.mappings`**: How to map API fields to Port entity properties. For more details on how the no-code Ocean custom integration works, see the [How it works](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview#how-it-works) section in the custom integration overview. **To configure the mappings:** 1. Go to your [connectors page](https://app.port.io/settings/data-sources). 2. Find your Rootly integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below. **Incidents mapping (click to expand)** ``` resources: - kind: /incidents selector: query: 'true' data_path: '.data' query_params: page[size]: "100" port: entity: mappings: identifier: .id title: .attributes.title blueprint: '"rootly-incident"' properties: title: .attributes.title status: .attributes.status severity: .attributes.severity.data.attributes.severity started_at: .attributes.started_at resolved_at: .attributes.resolved_at url: .attributes.url summary: .attributes.summary ``` Rootly API response format Rootly uses the JSON:API specification, so responses are structured with `data` arrays containing objects with `id`, `type`, and `attributes` fields. Use `.data` as the `data_path` and reference fields via `.attributes.fieldName`. **Services mapping (click to expand)** ``` resources: - kind: /services selector: query: 'true' data_path: '.data' query_params: page[size]: "100" port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"rootly-service"' properties: name: .attributes.name description: .attributes.description slug: .attributes.slug color: .attributes.color notify_emails: .attributes.notify_emails ``` **Teams mapping (click to expand)** ``` resources: - kind: /teams selector: query: 'true' data_path: '.data' query_params: page[size]: "100" port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"rootly-team"' properties: name: .attributes.name description: .attributes.description slug: .attributes.slug color: .attributes.color ``` **Users mapping (click to expand)** ``` resources: - kind: /users selector: query: 'true' data_path: '.data' query_params: page[size]: "100" port: entity: mappings: identifier: .id title: .attributes.full_name // .attributes.email blueprint: '"rootly-user"' properties: email: .attributes.email full_name: .attributes.full_name role: .attributes.role accepted_invite_at: .attributes.accepted_invite_at ``` 5. Click `Save` to save the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to customize your setup or test different API endpoints before committing to a configuration, use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md). **The interactive builder helps you:** 1. Test your Rootly API endpoints with live data. 2. Automatically detect the data structure and field types. 3. Generate blueprints and resource mappings tailored to your preferences. 4. Get installation commands with your configuration pre-filled. Simply provide your Rootly API details, and the builder will generate everything you need to install and create the integration in Port. ## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") ### Common issues[​](#common-issues "Direct link to Common issues") 1. **Authentication Errors**: Verify your Rootly API key has the correct permissions and is not expired. Ensure you're using a Global API Key for full access to all resources. 2. **Rate Limiting**: Rootly has API rate limits (3000 requests per minute per API key). The integration handles this automatically with retries. If you encounter frequent rate limit errors, consider reducing the sync frequency. 3. **Data Path Issues**: Rootly uses the JSON :API specification, so all responses have a `data` array. Ensure your `data_path` is set to `.data` for all endpoints. 4. **Pagination**: Rootly uses page-based pagination with `page[number]` and `page[size]` parameters. The integration handles this automatically when `paginationType` is set to `page`. 5. **Empty Responses**: If you're getting empty data, verify that your Rootly account has the resources you're trying to sync (incidents, services, teams, etc.). ## Limitations[​](#limitations "Direct link to Limitations") * The integration currently supports polling-based sync only (no real-time webhooks). * Rate limits apply (3000 requests per minute per API key). * Some Rootly resources may require specific permissions or subscription tiers. * The integration uses the JSON :API specification format, which structures responses differently from standard REST APIs. --- # ServiceNow ![ServiceNow icon](/img/guides/icons/ServiceNow.svg)![ServiceNow icon](/img/guides/icons/ServiceNow.svg) Loading version... Port's ServiceNow integration allows you to model ServiceNow resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Group](https://developer.servicenow.com/dev.do#!/reference/api/xanadu/rest/c_TableAPI#table-GET)[Service Catalog](https://developer.servicenow.com/dev.do#!/reference/api/xanadu/rest/c_TableAPI#table-GET)[Incident](https://developer.servicenow.com/dev.do#!/reference/api/xanadu/rest/c_TableAPI#table-GET)[Release Management](https://developer.servicenow.com/dev.do#!/reference/api/xanadu/rest/c_TableAPI#table-GET)[Vulnerability](https://developer.servicenow.com/dev.do#!/reference/api/xanadu/rest/c_TableAPI#table-GET)[Custom](https://developer.servicenow.com/dev.do#!/reference/api/xanadu/rest/c_TableAPI#table-GET) ## Core support [Live eventsSee supported resources](#set-up-live-events) Ingesting extra resources The ServiceNow integration uses the [ServiceNow Table API](https://developer.servicenow.com/dev.do#!/reference/api/xanadu/rest/c_TableAPI#table-GET) to ingest entities, which means you can ingest any resource that exists in the Table API, not just the ones listed above. Simply specify the `table name` as a new `kind` in the [Connectors configuration page](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#customize-your-integrations) and the records from the table will be ingested to Port. ## Setup[​](#setup "Direct link to Setup") ### Authentication methods[​](#authentication-methods "Direct link to Authentication methods") The ServiceNow integration supports two authentication methods: 1. **Basic authentication** - Uses a ServiceNow username and password. 2. **OAuth 2.0 client credentials** - Uses OAuth client ID and client secret for more secure authentication. OAuth authentication recommended OAuth 2.0 is the recommended authentication method as it provides better security and doesn't require sharing user credentials. The integration automatically handles token refresh and expiration. You can configure the authentication method by providing the appropriate credentials: * For **basic authentication**, provide `servicenowUsername` and `servicenowPassword`. * For **OAuth authentication**, provide `servicenowClientId` and `servicenowClientSecret`. The integration will automatically detect and use OAuth authentication if client credentials are provided. **Setting up OAuth in ServiceNow (click to expand)** To use OAuth authentication, you need to create an OAuth application endpoint in ServiceNow: 1. Log in to your ServiceNow instance as an administrator. 2. Navigate to **System OAuth** > **Application Registry**. 3. Click **New** to create a new application. 4. Select **Create an OAuth API endpoint for external clients**. 5. Fill in the following details: * **Name**: Give your application a descriptive name (e.g., "Port Integration"). * **Client ID**: This will be auto-generated, or you can specify a custom one. * **Client Secret**: This will be auto-generated. Make sure to copy it securely. * **Accessible from**: Select "All application scopes". 6. Click **Submit** to save the configuration. 7. Use the generated **Client ID** and **Client Secret** in your integration configuration. For detailed information about OAuth client credentials configuration in ServiceNow, refer to the [ServiceNow OAuth documentation](https://www.servicenow.com/docs/bundle/zurich-platform-security/page/integrate/machine-identity/task/configure-an-oauth-client-credential-grant.html). ### Installation methods[​](#installation-methods "Direct link to Installation methods") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Live events[​](#live-events "Direct link to Live events") The ServiceNow integration supports **live events** for real-time catalog updates. When enabled, the integration automatically creates [Business Rules](https://www.servicenow.com/docs/bundle/xanadu-application-development/page/script/business-rules/concept/c_BusinessRules.html) in your ServiceNow instance that forward record changes to Port via an Outbound REST Message. ### Enabling live events[​](#enabling-live-events "Direct link to Enabling live events") To enable live events, set `enableTablesLiveEventsWebhooks` to `true` in your integration configuration. When enabled, the integration creates Business Rules for every [supported table](#supported-resources) in your [mapping configuration](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure), or only for tables specified in `liveEventTables`. For self-hosted installations, you also need to configure `liveEvents.baseUrl` so that ServiceNow can reach your integration instance. See the [installation methods](#installation-methods) section for network configuration details. ### Supported resources[​](#supported-resources "Direct link to Supported resources") The following resources support live events. Each resource receives Business Rules for insert, update, and delete operations: | Resource | ServiceNow table | Supported events | | ------------------ | ------------------------ | ---------------------- | | Incident | `incident` | insert, update, delete | | Group | `sys_user_group` | insert, update, delete | | Service Catalog | `sc_catalog` | insert, update, delete | | Vulnerability | `sn_vul_vulnerable_item` | insert, update, delete | | Release Management | `release_project` | insert, update, delete | Custom tables Custom tables added to your mapping configuration that are not in the list above will not receive live events. They will still be ingested during scheduled resyncs. ### Delete events and entity identifiers[​](#delete-events-and-entity-identifiers "Direct link to Delete events and entity identifiers") For delete events to correctly resolve entities in your catalog, your mapping should use `.sys_id` as the entity identifier. `.sys_id` is the primary key for every ServiceNow record and is guaranteed to be present in all webhook payloads across all supported tables. For example, use `.sys_id` as the identifier for your resources: ``` port: entity: mappings: identifier: .sys_id title: .short_description blueprint: '"servicenowIncident"' ``` ### Securing webhooks[​](#securing-webhooks "Direct link to Securing webhooks") You can secure the webhook endpoint by setting the `webhookSecret` parameter. When configured, the integration automatically adds an `Authorization` header to the Outbound REST Message in ServiceNow and validates it on all incoming webhook requests. ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` resources: - kind: sys_user_group selector: query: 'true' apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' port: entity: mappings: identifier: .sys_id title: .name blueprint: '"servicenowGroup"' properties: description: .description isActive: .active createdOn: '.sys_created_on | (strptime("%Y-%m-%d %H:%M:%S") | strftime("%Y-%m-%dT%H:%M:%SZ"))' createdBy: .sys_created_by - kind: sc_catalog selector: query: 'true' apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' port: entity: mappings: identifier: .sys_id title: .title blueprint: '"servicenowCatalog"' properties: description: .description isActive: .active createdOn: '.sys_created_on | (strptime("%Y-%m-%d %H:%M:%S") | strftime("%Y-%m-%dT%H:%M:%SZ"))' createdBy: .sys_created_by - kind: incident selector: query: 'true' apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' port: entity: mappings: identifier: .sys_id title: .short_description blueprint: '"servicenowIncident"' properties: number: .number | tostring state: .state category: .category reopenCount: .reopen_count severity: .severity assignedTo: .assigned_to.link urgency: .urgency contactType: .contact_type createdOn: '.sys_created_on | (strptime("%Y-%m-%d %H:%M:%S") | strftime("%Y-%m-%dT%H:%M:%SZ"))' createdBy: .sys_created_by isActive: .active priority: .priority ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") GroupService CatalogIncidentRelease ManagementVulnerability Default mappingYAML ``` - kind: sys_user_group selector: query: "true" apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' port: entity: mappings: identifier: .sys_id title: .name blueprint: '"servicenowGroup"' properties: description: .description isActive: .active createdOn: '.sys_created_on | (strptime("%Y-%m-%d %H:%M:%S") | strftime("%Y-%m-%dT%H:%M:%SZ"))' createdBy: .sys_created_by ``` Sample payloadJSON ``` { "parent": "", "manager": "", "roles": "", "sys_mod_count": "0", "active": "true", "description": "\n\t\tGroup for all people who have the Analytics Admin role\n\t", "source": "", "sys_updated_on": "2020-03-17 11:39:14", "sys_tags": "", "type": "", "sys_id": "019ad92ec7230010393d265c95c260dd", "sys_updated_by": "admin", "cost_center": "", "default_assignee": "", "sys_created_on": "2020-03-17 11:39:14", "name": "Analytics Settings Managers", "exclude_manager": "false", "email": "", "include_members": "false", "sys_created_by": "admin" } ``` Port entityJSON ``` { "identifier": "019ad92ec7230010393d265c95c260dd", "title": "Analytics Settings Managers", "icon": "ServiceNow", "blueprint": "servicenowGroup", "team": [], "properties": { "description": "\n\t\tGroup for all people who have the Analytics Admin role\n\t", "isActive": true, "createdOn": "2020-03-17T11:39:14Z", "createdBy": "admin" }, "relations": {}, "createdAt": "2023-12-18T08:37:21.637Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-12-18T08:37:21.637Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: sc_catalog selector: query: "true" apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' port: entity: mappings: identifier: .sys_id title: .title blueprint: '"servicenowCatalog"' properties: description: .description isActive: .active createdOn: '.sys_created_on | (strptime("%Y-%m-%d %H:%M:%S") | strftime("%Y-%m-%dT%H:%M:%SZ"))' createdBy: .sys_created_by ``` Sample payloadJSON ``` { "manager": { "link": "https://dev229583.service-now.com/api/now/table/sys_user/6816f79cc0a8016401c5a33be04be441", "value": "6816f79cc0a8016401c5a33be04be441" }, "sys_mod_count": "0", "active": "true", "description": "Description for service catalog", "desktop_continue_shopping": "", "enable_wish_list": "false", "sys_updated_on": "2023-12-14 15:30:54", "sys_tags": "", "title": "Test Service Catalog", "sys_class_name": "sc_catalog", "desktop_image": "", "sys_id": "56e48e6a9743311083e6ff0de053af56", "sys_package": { "link": "https://dev229583.service-now.com/api/now/table/sys_package/global", "value": "global" }, "desktop_home_page": "", "sys_update_name": "sc_catalog_56e48e6a9743311083e6ff0de053af56", "sys_updated_by": "admin", "sys_created_on": "2023-12-14 15:30:54", "sys_name": "Test Service Catalog", "sys_scope": { "link": "https://dev229583.service-now.com/api/now/table/sys_scope/global", "value": "global" }, "editors": "", "sys_created_by": "admin", "sys_policy": "" } ``` Port entityJSON ``` { "identifier": "56e48e6a9743311083e6ff0de053af56", "title": "Test Service Catalog", "icon": "ServiceNow", "blueprint": "servicenowCatalog", "team": [], "properties": { "description": "Description for service catalog", "isActive": true, "createdOn": "2023-12-14T15:30:54Z", "createdBy": "admin" }, "relations": {}, "createdAt": "2023-12-18T08:37:28.087Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-12-18T08:37:28.087Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: incident selector: query: "true" apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' port: entity: mappings: identifier: .sys_id title: .short_description blueprint: '"servicenowIncident"' properties: number: .number | tostring state: .state category: .category reopenCount: .reopen_count severity: .severity assignedTo: .assigned_to.link urgency: .urgency contactType: .contact_type createdOn: '.sys_created_on | (strptime("%Y-%m-%d %H:%M:%S") | strftime("%Y-%m-%dT%H:%M:%SZ"))' createdBy: .sys_created_by isActive: .active priority: .priority ``` Sample payloadJSON ``` { "parent": "", "made_sla": "true", "caused_by": "", "watch_list": "", "upon_reject": "cancel", "sys_updated_on": "2016-12-14 02:46:44", "child_incidents": "0", "hold_reason": "", "origin_table": "", "task_effective_number": "INC0000060", "approval_history": "", "number": "INC0000060", "resolved_by": { "link": "https://dev229583.service-now.com/api/now/v1/table/sys_user/5137153cc611227c000bbd1bd8cd2007", "value": "5137153cc611227c000bbd1bd8cd2007" }, "sys_updated_by": "employee", "opened_by": { "link": "https://dev229583.service-now.com/api/now/v1/table/sys_user/681ccaf9c0a8016400b98a06818d57c7", "value": "681ccaf9c0a8016400b98a06818d57c7" }, "user_input": "", "sys_created_on": "2016-12-12 15:19:57", "sys_domain": { "link": "https://dev229583.service-now.com/api/now/v1/table/sys_user_group/global", "value": "global" }, "state": "7", "route_reason": "", "sys_created_by": "employee", "knowledge": "false", "order": "", "calendar_stc": "102197", "closed_at": "2016-12-14 02:46:44", "cmdb_ci": { "link": "https://dev229583.service-now.com/api/now/v1/table/cmdb_ci/109562a3c611227500a7b7ff98cc0dc7", "value": "109562a3c611227500a7b7ff98cc0dc7" }, "delivery_plan": "", "contract": "", "impact": "2", "active": "false", "work_notes_list": "", "business_service": { "link": "https://dev229583.service-now.com/api/now/v1/table/cmdb_ci_service/27d32778c0a8000b00db970eeaa60f16", "value": "27d32778c0a8000b00db970eeaa60f16" }, "business_impact": "", "priority": "3", "sys_domain_path": "/", "rfc": "", "time_worked": "", "expected_start": "", "opened_at": "2016-12-12 15:19:57", "business_duration": "1970-01-01 08:00:00", "group_list": "", "work_end": "", "caller_id": { "link": "https://dev229583.service-now.com/api/now/v1/table/sys_user/681ccaf9c0a8016400b98a06818d57c7", "value": "681ccaf9c0a8016400b98a06818d57c7" }, "reopened_time": "", "resolved_at": "2016-12-13 21:43:14", "approval_set": "", "subcategory": "email", "work_notes": "", "universal_request": "", "short_description": "Unable to connect to email", "close_code": "Solved (Permanently)", "correlation_display": "", "delivery_task": "", "work_start": "", "assignment_group": { "link": "https://dev229583.service-now.com/api/now/v1/table/sys_user_group/287ebd7da9fe198100f92cc8d1d2154e", "value": "287ebd7da9fe198100f92cc8d1d2154e" }, "additional_assignee_list": "", "business_stc": "28800", "cause": "", "description": "I am unable to connect to the email server. It appears to be down.", "origin_id": "", "calendar_duration": "1970-01-02 04:23:17", "close_notes": "This incident is resolved.", "notify": "1", "service_offering": "", "sys_class_name": "incident", "closed_by": { "link": "https://dev229583.service-now.com/api/now/v1/table/sys_user/681ccaf9c0a8016400b98a06818d57c7", "value": "681ccaf9c0a8016400b98a06818d57c7" }, "follow_up": "", "parent_incident": "", "sys_id": "1c741bd70b2322007518478d83673af3", "contact_type": "self-service", "reopened_by": "", "incident_state": "7", "urgency": "2", "problem_id": "", "company": { "link": "https://dev229583.service-now.com/api/now/v1/table/core_company/31bea3d53790200044e0bfc8bcbe5dec", "value": "31bea3d53790200044e0bfc8bcbe5dec" }, "reassignment_count": "2", "activity_due": "2016-12-13 01:26:36", "assigned_to": { "link": "https://dev229583.service-now.com/api/now/v1/table/sys_user/5137153cc611227c000bbd1bd8cd2007", "value": "5137153cc611227c000bbd1bd8cd2007" }, "severity": "3", "comments": "", "approval": "not requested", "sla_due": "", "comments_and_work_notes": "", "due_date": "", "sys_mod_count": "15", "reopen_count": "0", "sys_tags": "", "escalation": "0", "upon_approval": "proceed", "correlation_id": "", "location": "", "category": "inquiry" } ``` Port entityJSON ``` { "identifier": "INC0000060", "title": "Unable to connect to email", "icon": "ServiceNow", "blueprint": "servicenowIncident", "team": [], "properties": { "category": "inquiry", "reopenCount": "0", "severity": "3", "assignedTo": "https://dev229583.service-now.com/api/now/table/sys_user/5137153cc611227c000bbd1bd8cd2007", "urgency": "2", "contactType": "self-service", "createdOn": "2016-12-12T15:19:57Z", "createdBy": "employee", "isActive": false, "priority": "3" }, "relations": {}, "createdAt": "2023-12-15T14:52:06.347Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-12-15T15:34:18.248Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `apiQueryParams` | `object` | The query parameters used to filter resources from the ServiceNow API | `apiQueryParams` parameters | Parameter | Type | Description | | ----------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sysparmDisplayValue` | `string` | Determines the type of data returned, either the actual values from the database or the display values of the fields Default: `"true"`. Allowed values: `true`, `false`, `all`. | | `sysparmFields` | `string` | Comma-separated list of fields to return in the response (e.g. 'sys\_id,name,description,state') | | `sysparmExcludeReferenceLink` | `string` | Flag that indicates whether to exclude Table API links for reference fields Default: `"false"`. Allowed values: `true`, `false`. | | `sysparmQuery` | `string` | Encoded query used to filter the result set. Syntax: \\\\: Name of the table column to filter against\: =, !=, ^, ^OR, LIKE, STARTSWITH, ENDSWITH, ORDERBY\, ORDERBYDESC\\: Value to match againstQueries can be chained using ^ or ^OR for AND/OR logic. Example: active=true^nameLIKEincident^urgency=3 | Default mappingYAML ``` - kind: release_project selector: query: "true" apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' sysparmFields: 'sys_id,number,name,type,workflow_state,description,planned_start_date,planned_end_date,priority,risk,sys_created_on,sys_created_by,sys_updated_on,sys_updated_by,active' port: entity: mappings: identifier: .sys_id title: (.name // .number // "Release") blueprint: '"servicenowRelease"' properties: number: .number name: .name type: .type workflowState: .workflow_state description: .description priority: .priority risk: .risk plannedStartDate: .planned_start_date plannedEndDate: .planned_end_date createdOn: '.sys_created_on | (strptime("%Y-%m-%d %H:%M:%S") | strftime("%Y-%m-%dT%H:%M:%SZ"))' createdBy: .sys_created_by updatedOn: '.sys_updated_on | (strptime("%Y-%m-%d %H:%M:%S") | strftime("%Y-%m-%dT%H:%M:%SZ"))' updatedBy: .sys_updated_by isActive: .active ``` Sample payloadJSON ``` { "sys_id": "053892cb47357650360b3b12d16d43eb", "number": "REL0010001", "name": "Test Release Project - Port Integration", "type": "Feature", "workflow_state": "Complete", "description": "Test release project created for Port Ocean integration testing", "planned_start_date": "", "planned_end_date": "", "actual_start_date": "", "actual_end_date": "2025-12-18", "due_date": "", "requested_date": "", "priority": "4 - Low", "risk": "Low", "manager": "", "requested_by": "", "product": "", "phases": null, "phases_wanted": "true", "release_history": "2025-12-18 03:37:47 - System (Release history)\nPhase Build added to project\n\n2025-12-18 03:37:47 - System (Release history)\nPhase Plan added to project\n\n2025-12-18 03:37:47 - System (Release history)\nPhase Accept added to project\n\n2025-12-18 03:37:47 - System (Release history)\nPhase Release added to project\n\n2025-12-18 03:37:47 - System (Release history)\nPhase Deploy added to project\n\n2025-12-18 03:37:47 - System (Release history)\nPhase Back Out added to project\n", "notes": "", "short_description": "", "parent": "", "sys_created_on": "2025-12-18 03:37:46", "sys_created_by": "admin", "sys_updated_on": "2025-12-18 03:37:47", "sys_updated_by": "system", "active": "false" } ``` Port entityJSON ``` { "identifier": "053892cb47357650360b3b12d16d43eb", "title": "Test Release Project - Port Integration", "icon": "ServiceNow", "blueprint": "servicenowRelease", "team": [], "properties": { "number": "REL0010001", "name": "Test Release Project - Port Integration", "type": "Feature", "workflowState": "Complete", "description": "Test release project created for Port Ocean integration testing", "priority": "4 - Low", "risk": "Low", "plannedStartDate": "", "plannedEndDate": "", "createdOn": "2025-12-18T03:37:46Z", "createdBy": "admin", "updatedOn": "2025-12-18T03:37:47Z", "updatedBy": "system", "isActive": "false" }, "relations": {}, "createdAt": "2025-12-18T03:37:46.000Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2025-12-18T03:37:47.000Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: sn_vul_vulnerable_item selector: query: "true" apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' sysparmFields: 'sys_id,state,first_found,last_found,priority,risk_score,sys_created_on,sys_created_by,sys_updated_on,sys_updated_by,active' port: entity: mappings: identifier: .sys_id title: (.number // "Vulnerability " + .sys_id) blueprint: '"servicenowVulnerability"' properties: state: .state priority: .priority riskScore: .risk_score firstFound: .first_found lastFound: .last_found createdOn: .sys_created_on createdBy: .sys_created_by updatedOn: .sys_updated_on updatedBy: .sys_updated_by isActive: .active ``` Sample payloadJSON ``` { "sys_id": "b7ad7e0347b1f650360b3b12d16d434f", "number": "VIT0010001", "state": "Under Investigation", "priority": "2 - High", "risk_score": "75", "first_found": "2024-01-15", "last_found": "2024-12-18", "sys_created_on": "2025-12-18 06:21:33", "sys_created_by": "admin", "sys_updated_on": "2025-12-18 06:21:33", "sys_updated_by": "admin", "active": "true" } ``` Port entityJSON ``` { "identifier": "b7ad7e0347b1f650360b3b12d16d434f", "title": "VIT0010001", "icon": "ServiceNow", "blueprint": "servicenowVulnerability", "team": [], "properties": { "state": "Under Investigation", "priority": "2 - High", "riskScore": "75", "firstFound": "2024-01-15", "lastFound": "2024-12-18", "createdOn": "2025-12-18 06:21:33", "createdBy": "admin", "updatedOn": "2025-12-18 06:21:33", "updatedBy": "admin", "isActive": "true" }, "relations": {}, "createdAt": "2025-12-18T06:21:33.000Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2025-12-18T06:21:33.000Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 ServiceNow 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 ServiceNow 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If ServiceNow is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that ServiceNow can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Filter ServiceNow resources[​](#filter-servicenow-resources "Direct link to Filter ServiceNow resources") Port's ServiceNow integration provides an option to filter the data that is retrieved from the ServiceNow Table API using the following attributes: 1. `sysparmDisplayValue`: Determines the type of data returned, either the actual values from the database or the display values of the fields. The default is `true` 2. `sysparmFields`: Comma-separated list of fields to return in the response 3. `sysparmExcludeReferenceLink`: Flag that indicates whether to exclude Table API links for reference fields. The default is `false` 4. `sysparmQuery`: Encoded query used to filter the result set. The syntax is ``: 1. ``: Name of the table column to filter against 2. ``: =, !=, ^, ^OR, LIKE, STARTSWITH, ENDSWITH, `ORDERBY`, `ORDERBYDESC` 3. ``: Value to match against Queries can be chained using ^ or ^OR for AND/OR logic. An example query could be this: `active=true^nameLIKEdev^urgency=3` which returns all active incidents with an urgency level of 3 and have a name like `dev` The filtering attributes described above can be enabled using the `selector.apiQueryParams` path, for example: ``` - kind: selector: query: "true" apiQueryParams: sysparmDisplayValue: 'true' sysparmExcludeReferenceLink: 'false' sysparmQuery: active=true^nameLIKEdev^urgency=3 sysparmFields: sys_id,priority,created_by,state,active ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=ServiceNow). --- # Statuspage ![StatusPage icon](/img/guides/icons/StatusPage.svg)![StatusPage icon](/img/guides/icons/StatusPage.svg) Loading version... Port's Statuspage integration allows you to model Statuspage resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Page](https://developer.statuspage.io/#tag/pages)[Component Group](https://developer.statuspage.io/#tag/component-groups)[Component](https://developer.statuspage.io/#tag/components)[Incident](https://developer.statuspage.io/#tag/incidents)[Incident Update](https://developer.statuspage.io/#tag/incident-updates) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A Statuspage account with permissions to create API keys. * Your Port user role is set to `Admin`. ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: statuspage selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"statuspage"' properties: page_description: .page_description headline: .headline branding: .branding status_indicator: .status_indicator status_description: .status_description subdomain: .subdomain domain: .domain url: .url allow_page_subscribers: .allow_page_subscribers allow_incident_subscribers: .allow_incident_subscribers allow_email_subscribers: .allow_email_subscribers allow_sms_subscribers: .allow_sms_subscribers allow_rss_atom_feeds: .allow_rss_atom_feeds allow_webhook_subscribers: .allow_webhook_subscribers time_zone: .time_zone createdAt: .created_at updatedAt: .updated_at - kind: component_group selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageComponentGroup"' properties: description: .description position: .position createdAt: .created_at updatedAt: .updated_at relations: statuspage: .page_id - kind: component selector: query: .group == false port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageComponent"' properties: description: .description position: .position status: .status showcase: .showcase only_show_if_degraded: .only_show_if_degraded startDate: .start_date | if . == null then null else (strptime("%Y-%m-%d") | todateiso8601) end createdAt: .created_at updatedAt: .updated_at relations: componentGroup: .group_id statuspage: .page_id - kind: incident selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageIncident"' properties: status: .status impact: .impact createdAt: .created_at updatedAt: .updated_at startedAt: .started_at resolvedAt: .resolved_at shortlink: .shortlink scheduled_for: .scheduled_for scheduled_until: .scheduled_until scheduled_remind_prior: .scheduled_remind_prior scheduled_reminded_at: .scheduled_reminded_at impact_override: .impact_override scheduled_auto_in_progress: .scheduled_auto_in_progress scheduled_auto_completed: .scheduled_auto_completed metadata: .metadata reminder_intervals: .reminder_intervals postmortem_body: .postmortem_body postmortem_body_last_updated_at: .postmortem_body_last_updated_at postmortem_ignored: .postmortem_ignored postmortem_published_at: .postmortem_published_at postmortem_notified_subscribers: .postmortem_notified_subscribers postmortem_notified_twitter: .postmortem_notified_twitter relations: components: '[.components[].id]' statuspage: .page_id - kind: incident_update selector: query: 'true' port: entity: mappings: identifier: .id title: .body blueprint: '"statuspageIncidentUpdate"' properties: status: .status body: .body createdAt: .created_at displayAt: .display_at deliverNotifications: .deliver_notifications wantsTwitterUpdate: .wants_twitter_update tweet_id: .tweet_id custom_tweet: .custom_tweet relations: incident: .incident_id affectedComponents: '[.affected_components[].code]' ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/examples/.md). PageComponent GroupComponentIncidentIncident Update Default mappingYAML ``` - kind: statuspage selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"statuspage"' properties: page_description: .page_description headline: .headline branding: .branding status_indicator: .status_indicator status_description: .status_description subdomain: .subdomain domain: .domain url: .url allow_page_subscribers: .allow_page_subscribers allow_incident_subscribers: .allow_incident_subscribers allow_email_subscribers: .allow_email_subscribers allow_sms_subscribers: .allow_sms_subscribers allow_rss_atom_feeds: .allow_rss_atom_feeds allow_webhook_subscribers: .allow_webhook_subscribers time_zone: .time_zone createdAt: .created_at updatedAt: .updated_at ``` Sample payloadJSON ``` { "id": "8m2l51279rpl", "created_at": "2024-06-07T01:12:00Z", "updated_at": "2024-06-26T03:49:25Z", "name": "Ctrlv Solutions", "page_description": null, "headline": null, "branding": "basic", "subdomain": "ctrlvsolutions", "domain": null, "url": null, "support_url": null, "hidden_from_search": false, "allow_page_subscribers": true, "allow_incident_subscribers": true, "allow_email_subscribers": true, "allow_sms_subscribers": true, "allow_rss_atom_feeds": true, "allow_webhook_subscribers": false, "notifications_from_email": null, "notifications_email_footer": "You received this email because you are subscribed to Ctrlv Solutions's service status notifications.", "activity_score": 499, "twitter_username": null, "viewers_must_be_team_members": false, "ip_restrictions": null, "city": null, "state": null, "country": null, "time_zone": "UTC", "css_body_background_color": "ffffff", "css_font_color": "333333", "css_light_font_color": "757575", "css_greens": "1e8542", "css_yellows": "f1c40f", "css_oranges": "b35e14", "css_blues": "207ab6", "css_reds": "de2f1b", "css_border_color": "E0E0E0", "css_graph_color": "3498db", "css_link_color": "207ab6", "css_no_data": "b3bac5", "favicon_logo": { "updated_at": null, "size": null, "url": "" }, "transactional_logo": { "updated_at": null, "original_url": "", "size": null, "normal_url": "", "retina_url": "" }, "hero_cover": { "updated_at": null, "original_url": "", "size": null, "normal_url": "", "retina_url": "" }, "email_logo": { "updated_at": null, "original_url": "", "size": null, "normal_url": "", "retina_url": "" }, "twitter_logo": { "updated_at": null, "size": null, "url": "" } } ``` Port entityJSON ``` { "identifier": "8m2l51279rpl", "title": "Ctrlv Solutions", "blueprint": "statuspage", "icon": "StatusPage", "team": [], "properties": { "time_zone": "UTC", "updatedAt": "2024-06-21T01:31:52Z", "allow_sms_subscribers": true, "allow_webhook_subscribers": false, "allow_page_subscribers": true, "allow_rss_atom_feeds": true, "branding": "basic", "allow_incident_subscribers": true, "subdomain": "ctrlvsolutions", "createdAt": "2024-06-07T01:12:00Z", "allow_email_subscribers": true }, "relations": {}, "createdAt": "2024-06-25T07:59:28.716Z", "createdBy": "", "updatedAt": "2024-06-25T07:59:28.716Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: component_group selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageComponentGroup"' properties: description: .description position: .position createdAt: .created_at updatedAt: .updated_at relations: statuspage: .page_id ``` Sample payloadJSON ``` { "id": "9w477s340zqt", "page_id": "8m2l51279rpl", "name": "Backend", "description": null, "components": [ "3chbmx8qmlny" ], "position": 7, "created_at": "2024-06-06T15:51:24Z", "updated_at": "2024-06-06T15:51:24Z" } ``` Port entityJSON ``` { "identifier": "9w477s340zqt", "title": "Backend", "blueprint": "statuspageComponentGroup", "icon": "StatusPage", "team": [], "properties": { "updatedAt": "2024-06-06T15:51:24Z", "position": 7, "createdAt": "2024-06-06T15:51:24Z" }, "relations": { "statuspage": "8m2l51279rpl" }, "createdAt": "2024-06-25T07:59:33.363Z", "createdBy": "", "updatedAt": "2024-06-25T07:59:33.363Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: component selector: query: .group == false port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageComponent"' properties: description: .description position: .position status: .status showcase: .showcase only_show_if_degraded: .only_show_if_degraded startDate: .start_date | if . == null then null else (strptime("%Y-%m-%d") | todateiso8601) end createdAt: .created_at updatedAt: .updated_at relations: componentGroup: .group_id statuspage: .page_id ``` Sample payloadJSON ``` { "id": "y6ssccsqgy8s", "page_id": "8m2l51279rpl", "group_id": null, "created_at": "2024-06-07T01:12:00Z", "updated_at": "2024-06-07T01:12:00Z", "group": false, "name": "API Gateway", "description": null, "position": 1, "status": "operational", "showcase": true, "only_show_if_degraded": false, "automation_email": "component+4912b1b6b16b44f7be966d1389ca7dd1@notifications.statuspage.io", "start_date": "2024-06-07" } ``` Port entityJSON ``` { "identifier": "y6ssccsqgy8s", "title": "API Gateway", "blueprint": "statuspageComponent", "icon": "StatusPage", "team": [], "properties": { "startDate": "2024-06-07T00:00:00Z", "only_show_if_degraded": false, "status": "operational", "updatedAt": "2024-06-07T01:12:00Z", "position": 1, "showcase": true, "createdAt": "2024-06-07T01:12:00Z" }, "relations": { "statuspage": "8m2l51279rpl", "componentGroup": null }, "createdAt": "2024-06-25T07:59:35.830Z", "createdBy": "", "updatedAt": "2024-06-25T07:59:35.830Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: incident selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageIncident"' properties: status: .status impact: .impact createdAt: .created_at updatedAt: .updated_at startedAt: .started_at resolvedAt: .resolved_at shortlink: .shortlink scheduled_for: .scheduled_for scheduled_until: .scheduled_until scheduled_remind_prior: .scheduled_remind_prior scheduled_reminded_at: .scheduled_reminded_at impact_override: .impact_override scheduled_auto_in_progress: .scheduled_auto_in_progress scheduled_auto_completed: .scheduled_auto_completed metadata: .metadata reminder_intervals: .reminder_intervals postmortem_body: .postmortem_body postmortem_body_last_updated_at: .postmortem_body_last_updated_at postmortem_ignored: .postmortem_ignored postmortem_published_at: .postmortem_published_at postmortem_notified_subscribers: .postmortem_notified_subscribers postmortem_notified_twitter: .postmortem_notified_twitter relations: components: '[.components[].id]' statuspage: .page_id ``` Sample payloadJSON ``` { "id": "f5v3b6dr7k53", "components": [ { "id": "7ddtptqkb02y", "page_id": "8m2l51279rpl", "group_id": null, "created_at": "2024-06-06T15:47:06Z", "updated_at": "2024-06-06T15:47:06Z", "group": false, "name": "API Gateway", "description": null, "position": 1, "status": "operational", "showcase": true, "only_show_if_degraded": false, "automation_email": "component+249149b3d4f94d599179e9a5a2f9762d@notifications.statuspage.io", "start_date": "2024-06-06" }, { "id": "3chbmx8qmlny", "page_id": "8m2l51279rpl", "group_id": "9w477s340zqt", "created_at": "2024-06-06T15:51:24Z", "updated_at": "2024-06-06T15:51:24Z", "group": false, "name": "Fraud Detection Service", "description": null, "position": 1, "status": "operational", "showcase": true, "only_show_if_degraded": false, "automation_email": "component+3224d887fad941c8a39a87c6a67efcc4@notifications.statuspage.io", "start_date": "2024-06-06" }, { "id": "kvpxtsp2k1lf", "page_id": "8m2l51279rpl", "group_id": null, "created_at": "2024-06-06T15:49:37Z", "updated_at": "2024-06-07T01:04:38Z", "group": false, "name": "Payment Service", "description": null, "position": 5, "status": "partial_outage", "showcase": true, "only_show_if_degraded": false, "automation_email": "component+e9ed656c064e4d15935fc611dd4c04db@notifications.statuspage.io", "start_date": "2024-06-06" }, { "id": "3h6c0mc56qdr", "page_id": "8m2l51279rpl", "group_id": null, "created_at": "2024-06-06T15:52:14Z", "updated_at": "2024-06-06T15:52:14Z", "group": false, "name": "Jira Administration", "description": null, "position": 8, "status": "operational", "showcase": false, "only_show_if_degraded": false, "automation_email": "component+3cf3309f51f14f3aa7b024cf4982edda@notifications.statuspage.io", "start_date": null } ], "created_at": "2024-06-07T01:00:17Z", "impact": "major", "impact_override": "major", "incident_updates": [ { "id": "rgm45b89jz31", "incident_id": "f5v3b6dr7k53", "affected_components": [ { "code": "7ddtptqkb02y", "name": "API Gateway", "old_status": "operational", "new_status": "operational" }, { "code": "kvpxtsp2k1lf", "name": "Payment Service", "old_status": "partial_outage", "new_status": "partial_outage" }, { "code": "3chbmx8qmlny", "name": "Backend - Fraud Detection Service", "old_status": "operational", "new_status": "operational" }, { "code": "3h6c0mc56qdr", "name": "Jira Administration", "old_status": "operational", "new_status": "operational" } ], "body": "Resolved", "created_at": "2024-06-07T22:26:34.370Z", "custom_tweet": null, "deliver_notifications": true, "display_at": "2024-06-07T22:26:34.370Z", "status": "resolved", "tweet_id": null, "twitter_updated_at": null, "updated_at": "2024-06-07T22:26:34.370Z", "wants_twitter_update": false }, { "id": "p19ll31ysh49", "incident_id": "f5v3b6dr7k53", "affected_components": [ { "code": "7ddtptqkb02y", "name": "API Gateway", "old_status": "operational", "new_status": "operational" }, { "code": "kvpxtsp2k1lf", "name": "Payment Service", "old_status": "partial_outage", "new_status": "partial_outage" }, { "code": "3chbmx8qmlny", "name": "Backend - Fraud Detection Service", "old_status": "operational", "new_status": "operational" } ], "body": "checking components", "created_at": "2024-06-07T22:22:28.761Z", "custom_tweet": null, "deliver_notifications": true, "display_at": "2024-06-07T22:22:28.761Z", "status": "monitoring", "tweet_id": null, "twitter_updated_at": null, "updated_at": "2024-06-07T22:22:28.761Z", "wants_twitter_update": false }, { "id": "gkjdpm52nm97", "incident_id": "f5v3b6dr7k53", "affected_components": null, "body": "We are currently experiencing timeout errors with our API Gateway. Some API requests may fail or take longer to process. Our team is investigating the issue.", "created_at": "2024-06-07T01:00:17.309Z", "custom_tweet": null, "deliver_notifications": true, "display_at": "2024-06-07T01:00:17.309Z", "status": "investigating", "tweet_id": null, "twitter_updated_at": null, "updated_at": "2024-06-07T01:00:17.309Z", "wants_twitter_update": false } ], "metadata": { "port": { "runId": "r_hUFsDXiY5DzF3sjf", "trigger": "Object" } }, "monitoring_at": "2024-06-07T22:22:28Z", "name": "API Gateway Timeout Errors", "page_id": "8m2l51279rpl", "postmortem_body": null, "postmortem_body_last_updated_at": null, "postmortem_ignored": false, "postmortem_notified_subscribers": false, "postmortem_notified_twitter": false, "postmortem_published_at": null, "resolved_at": "2024-06-07T22:26:34Z", "scheduled_auto_completed": false, "scheduled_auto_in_progress": false, "scheduled_for": null, "auto_transition_deliver_notifications_at_end": null, "auto_transition_deliver_notifications_at_start": null, "auto_transition_to_maintenance_state": null, "auto_transition_to_operational_state": null, "scheduled_remind_prior": false, "scheduled_reminded_at": null, "scheduled_until": null, "shortlink": "https://stspg.io/t4szpmm54wxx", "status": "resolved", "updated_at": "2024-06-07T22:26:34Z", "reminder_intervals": null } ``` Port entityJSON ``` { "identifier": "f5v3b6dr7k53", "title": "API Gateway Timeout Errors", "blueprint": "statuspageIncident", "icon": "StatusPage", "team": [], "properties": { "scheduled_auto_completed": false, "createdAt": "2024-06-07T01:00:17.239Z", "metadata": { "port": { "runId": "r_hUFsDXiY5DzF3sjf", "trigger": "Object" } }, "impact": "major", "updatedAt": "2024-06-07T22:26:34.383Z", "status": "resolved", "shortlink": "https://stspg.io/t4szpmm54wxx", "postmortem_ignored": false, "startedAt": "2024-06-07T01:00:17.228Z", "scheduled_auto_in_progress": false, "postmortem_notified_subscribers": false, "scheduled_remind_prior": false, "resolvedAt": "2024-06-07T22:26:34.370Z", "postmortem_notified_twitter": false, "category": "incident" }, "relations": { "components": [ "7ddtptqkb02y", "3chbmx8qmlny", "kvpxtsp2k1lf", "3h6c0mc56qdr" ], "statuspage": "8m2l51279rpl" }, "createdAt": "2024-06-25T08:00:39.222Z", "createdBy": "", "updatedAt": "2024-06-25T08:00:39.222Z", "updatedBy": "" } ``` Default mappingYAML ``` - kind: incident_update selector: query: 'true' port: entity: mappings: identifier: .id title: .body blueprint: '"statuspageIncidentUpdate"' properties: status: .status body: .body createdAt: .created_at displayAt: .display_at deliverNotifications: .deliver_notifications wantsTwitterUpdate: .wants_twitter_update tweet_id: .tweet_id custom_tweet: .custom_tweet relations: incident: .incident_id affectedComponents: '[.affected_components[].code]' ``` Sample payloadJSON ``` { "id": "gkjdpm52nm97", "incident_id": "f5v3b6dr7k53", "affected_components": null, "body": "We are currently experiencing timeout errors with our API Gateway. Some API requests may fail or take longer to process. Our team is investigating the issue.", "created_at": "2024-06-07T01:00:17.309Z", "custom_tweet": null, "deliver_notifications": true, "display_at": "2024-06-07T01:00:17.309Z", "status": "investigating", "tweet_id": null, "twitter_updated_at": null, "updated_at": "2024-06-07T01:00:17.309Z", "wants_twitter_update": false } ``` Port entityJSON ``` { "identifier": "gkjdpm52nm97", "title": "We are currently experiencing timeout errors with our API Gateway. Some API requests may fail or take longer to process. Our team is investigating the issue.", "blueprint": "statuspageIncidentUpdate", "icon": "StatusPage", "team": [], "properties": { "status": "investigating", "wantsTwitterUpdate": false, "body": "We are currently experiencing timeout errors with our API Gateway. Some API requests may fail or take longer to process. Our team is investigating the issue.", "deliverNotifications": true, "createdAt": "2024-06-07T01:00:17.309Z", "displayAt": "2024-06-07T01:00:17.309Z", "category": "incident" }, "relations": { "incident": "f5v3b6dr7k53", "affectedComponents": [] }, "createdAt": "2024-06-25T08:00:46.083Z", "createdBy": "", "updatedAt": "2024-06-25T08:00:46.083Z", "updatedBy": "" } ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Examples[​](#examples "Direct link to Examples") For blueprint and configuration examples across all supported resource kinds, see the [Statuspage examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/examples/.md). To view and test the integration's mapping against live API responses, use the **jq playground** in your [connectors page](https://app.port.io/settings/data-sources). --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ## Page[​](#page "Direct link to Page") **Page blueprint (click to expand)** Create in Port ``` { "identifier": "statuspage", "title": "Status Page", "description": "A Statuspage for communicating service status and incidents.", "schema": { "properties": { "page_description": { "type": "string", "title": "Page Description", "description": "Description of the page (optional)." }, "headline": { "type": "string", "title": "Headline", "description": "A short headline for the Statuspage (optional)." }, "branding": { "type": "string", "title": "Branding", "description": "Branding level of the Statuspage (e.g., 'basic')." }, "status_indicator": { "type": "string", "title": "Status Indicator", "enum": ["none", "minor", "major", "critical"], "enumColors": { "none": "green", "minor": "yellow", "major": "orange", "critical": "red" }, "description": "The current status of the page." }, "status_description": { "type": "string", "title": "Status Description", "description": "Description of the current status (optional)." }, "subdomain": { "type": "string", "title": "Subdomain", "description": "The subdomain used for the Statuspage URL (e.g., 'appcellon')." }, "domain": { "type": "string", "title": "Custom Domain", "description": "Custom domain name for the Statuspage (optional)." }, "url": { "type": "string", "format": "url", "title": "Statuspage URL", "description": "Full URL of the Statuspage (optional)." }, "allow_page_subscribers": { "type": "boolean", "title": "Allow Page Subscribers", "description": "Whether to allow users to subscribe to page updates." }, "allow_incident_subscribers": { "type": "boolean", "title": "Allow Incident Subscribers", "description": "Whether to allow users to subscribe to specific incidents." }, "allow_email_subscribers": { "type": "boolean", "title": "Allow Email Subscribers", "description": "Whether to allow email subscriptions." }, "allow_sms_subscribers": { "type": "boolean", "title": "Allow SMS Subscribers", "description": "Whether to allow SMS subscriptions." }, "allow_rss_atom_feeds": { "type": "boolean", "title": "Allow RSS/Atom Feeds", "description": "Whether to allow RSS or Atom feeds." }, "allow_webhook_subscribers": { "type": "boolean", "title": "Allow Webhook Subscribers", "description": "Whether to allow webhook subscriptions." }, "time_zone": { "type": "string", "title": "Time Zone", "description": "The time zone used for the Statuspage." }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "When the Statuspage was created." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Last Updated At", "description": "When the Statuspage was last updated." } } }, "calculationProperties": {}, "aggregationProperties": { "criticalOpenIssues": { "title": "Services with Degraded Performance", "type": "number", "target": "statuspageComponent", "query": { "combinator": "and", "rules": [ { "property": "status", "operator": "=", "value": "degraded_performance" } ] }, "calculationSpec": { "calculationBy": "entities", "func": "count" } } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: statuspage selector: query: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"statuspage"' properties: page_description: .page_description headline: .headline branding: .branding status_indicator: .status_indicator status_description: .status_description subdomain: .subdomain domain: .domain url: .url allow_page_subscribers: .allow_page_subscribers allow_incident_subscribers: .allow_incident_subscribers allow_email_subscribers: .allow_email_subscribers allow_sms_subscribers: .allow_sms_subscribers allow_rss_atom_feeds: .allow_rss_atom_feeds allow_webhook_subscribers: .allow_webhook_subscribers time_zone: .time_zone createdAt: .created_at updatedAt: .updated_at ``` ## Component Group[​](#component-group "Direct link to Component Group") **Component Group blueprint (click to expand)** Create in Port ``` { "identifier": "statuspageComponentGroup", "title": "Statuspage Component Group", "description": "A group of related components on a Statuspage.", "schema": { "properties": { "description": { "type": "string", "title": "Description", "description": "Description of the group (optional)." }, "position": { "type": "number", "title": "Position", "description": "Order of the group on the Statuspage." }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "When the group was created." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Last Updated At", "description": "When the group was last updated." } } }, "calculationProperties": {}, "relations": { "statuspage": { "target": "statuspage", "required": false, "title": "Status Page", "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: component_group selector: query: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageComponentGroup"' properties: description: .description position: .position createdAt: .created_at updatedAt: .updated_at relations: statuspage: .page_id ``` ## Component[​](#component "Direct link to Component") **Component blueprint (click to expand)** Create in Port ``` { "identifier": "statuspageComponent", "title": "Statuspage Component", "icon": "Box", "description": "A component representing a specific part of a system or service on a Statuspage.", "schema": { "properties": { "description": { "type": "string", "title": "Description", "description": "Description of the component (optional)." }, "status": { "type": "string", "title": "Current Status", "enum": [ "operational", "degraded_performance", "partial_outage", "major_outage" ], "enumColors": { "operational": "green", "degraded_performance": "yellow", "partial_outage": "orange", "major_outage": "red" }, "description": "The operational status of the component." }, "position": { "type": "number", "title": "Position", "description": "Order of the component within its group." }, "showcase": { "type": "boolean", "title": "Showcase", "description": "Whether to display the component prominently." }, "only_show_if_degraded": { "type": "boolean", "title": "Only Show If Degraded", "description": "Whether to display the component only when degraded." }, "startDate": { "type": "string", "format": "date-time", "title": "Start Date", "description": "The date when the component tracking started." }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "When the component was created." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Last Updated At", "description": "When the component was last updated." } } }, "calculationProperties": {}, "relations": { "componentGroup": { "target": "statuspageComponentGroup", "required": false, "title": "Component Group", "many": false }, "statuspage": { "target": "statuspage", "required": false, "title": "Status Page", "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: component selector: query: ".group == false" port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageComponent"' properties: description: .description position: .position status: .status showcase: .showcase only_show_if_degraded: .only_show_if_degraded startDate: '.start_date | if . == null then null else (strptime("%Y-%m-%d") | todateiso8601) end' createdAt: .created_at updatedAt: .updated_at relations: componentGroup: .group_id statuspage: .page_id ``` ## Incident[​](#incident "Direct link to Incident") **Incident blueprint (click to expand)** Create in Port ``` { "identifier": "statuspageIncident", "title": "Statuspage Incident", "icon": "Alert", "description": "An incident reported on a Statuspage.", "schema": { "properties": { "status": { "type": "string", "title": "Current Status", "enum": [ "investigating", "identified", "monitoring", "resolved", "postmortem", "scheduled", "in_progress", "verifying", "completed" ], "enumColors": { "investigating": "blue", "identified": "orange", "monitoring": "yellow", "resolved": "green", "postmortem": "purple", "scheduled": "lightGray", "in_progress": "blue", "verifying": "yellow", "completed": "green" }, "description": "Current status of the incident." }, "impact": { "type": "string", "title": "Impact", "enum": ["none", "minor", "major", "critical"], "enumColors": { "none": "green", "minor": "yellow", "major": "orange", "critical": "red" }, "description": "The impact level of the incident." }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "When the incident was first reported." }, "updatedAt": { "type": "string", "format": "date-time", "title": "Last Updated At", "description": "When the incident was last updated." }, "startedAt": { "type": "string", "format": "date-time", "title": "Started At", "description": "When the incident actually began." }, "resolvedAt": { "type": "string", "format": "date-time", "title": "Resolved At", "description": "When the incident was resolved (null if ongoing)." }, "shortlink": { "type": "string", "format": "url", "title": "Short Link", "description": "A shortened URL for sharing the incident status page." }, "postmortemPublishedAt": { "type": "string", "format": "date-time", "title": "Postmortem Published At", "description": "When the postmortem analysis was published (null if not yet published)." }, "scheduled_for": { "type": "string", "format": "date-time", "title": "Scheduled For", "description": "Start time for a scheduled incident (null if not scheduled)." }, "scheduled_until": { "type": "string", "format": "date-time", "title": "Scheduled Until", "description": "End time for a scheduled incident (null if not scheduled)." }, "scheduled_remind_prior": { "type": "boolean", "title": "Scheduled Remind Prior", "description": "Whether to send a reminder before a scheduled incident." }, "scheduled_reminded_at": { "type": "string", "format": "date-time", "title": "Scheduled Reminded At", "description": "When the reminder for a scheduled incident was sent (null if not applicable)." }, "scheduled_auto_in_progress": { "type": "boolean", "title": "Scheduled Auto In Progress", "description": "Whether to automatically transition the incident to 'in progress'." }, "scheduled_auto_completed": { "type": "boolean", "title": "Scheduled Auto Completed", "description": "Whether to automatically mark the incident as completed." }, "metadata": { "type": "object", "title": "Metadata", "description": "Custom metadata associated with the incident." }, "reminder_intervals": { "type": "string", "title": "Reminder Intervals", "description": "Intervals for sending reminders for a scheduled incident (null if not applicable)." }, "postmortem_body": { "type": "string", "format": "markdown", "title": "Postmortem", "description": "The content of the postmortem analysis." }, "postmortem_body_last_updated_at": { "type": "string", "format": "date-time", "title": "Postmortem Last Updated At", "description": "When the postmortem body was last updated (null if not applicable)." }, "postmortem_ignored": { "type": "boolean", "title": "Postmortem Ignored", "description": "Whether the postmortem has been ignored." }, "postmortem_published_at": { "type": "string", "format": "date-time", "title": "Postmortem Published At", "description": "When the postmortem was published (null if not yet published)." }, "postmortem_notified_subscribers": { "type": "boolean", "title": "Postmortem Notified Subscribers", "description": "Whether subscribers were notified about the postmortem." }, "postmortem_notified_twitter": { "type": "boolean", "title": "Postmortem Notified Twitter", "description": "Whether the postmortem was announced on Twitter." } } }, "calculationProperties": { "category": { "title": "Category", "description": "Category of Incident", "calculation": ".properties | .status as $status | if ($status | IN(\"scheduled\", \"in_progress\", \"verifying\", \"completed\")) then \"maintenance\" else \"incident\" end", "type": "string", "colorized": true, "colors": { "maintenance": "bronze", "incident": "red" } } }, "relations": { "components": { "target": "statuspageComponent", "required": false, "title": "Affected Components", "many": true }, "statuspage": { "target": "statuspage", "required": false, "title": "Status Page", "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: incident selector: query: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"statuspageIncident"' properties: status: .status impact: .impact createdAt: .created_at updatedAt: .updated_at startedAt: .started_at resolvedAt: .resolved_at shortlink: .shortlink scheduled_for: .scheduled_for scheduled_until: .scheduled_until scheduled_remind_prior: .scheduled_remind_prior scheduled_reminded_at: .scheduled_reminded_at impact_override: .impact_override scheduled_auto_in_progress: .scheduled_auto_in_progress scheduled_auto_completed: .scheduled_auto_completed metadata: .metadata reminder_intervals: .reminder_intervals postmortem_body: .postmortem_body postmortem_body_last_updated_at: .postmortem_body_last_updated_at postmortem_ignored: .postmortem_ignored postmortem_published_at: .postmortem_published_at postmortem_notified_subscribers: .postmortem_notified_subscribers postmortem_notified_twitter: .postmortem_notified_twitter relations: components: "[.components[].id]" statuspage: .page_id ``` ## Incident Update[​](#incident-update "Direct link to Incident Update") **Incident Update blueprint (click to expand)** Create in Port ``` { "identifier": "statuspageIncidentUpdate", "title": "Statuspage Incident Update", "icon": "Alert", "description": "An update to a Statuspage incident.", "schema": { "properties": { "status": { "type": "string", "title": "Update Status", "enum": [ "investigating", "identified", "monitoring", "resolved", "postmortem", "scheduled", "in_progress", "verifying", "completed" ], "enumColors": { "investigating": "blue", "identified": "orange", "monitoring": "yellow", "resolved": "green", "postmortem": "purple", "scheduled": "lightGray", "in_progress": "blue", "verifying": "yellow", "completed": "green" }, "description": "The status of the incident at the time of the update." }, "body": { "type": "string", "title": "Update", "description": "The message content of the update." }, "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "When the update was created." }, "displayAt": { "type": "string", "format": "date-time", "title": "Display At", "description": "When the update was displayed on the Statuspage." }, "deliverNotifications": { "type": "boolean", "title": "Deliver Notifications", "description": "Whether notifications were sent for this update." }, "wantsTwitterUpdate": { "type": "boolean", "title": "Wants Twitter Update", "description": "Whether a Twitter update was requested." }, "tweet_id": { "type": "string", "title": "Tweet ID", "description": "The ID of the tweet associated with this update (if any)." }, "custom_tweet": { "type": "string", "title": "Custom Tweet", "description": "The custom text used for the tweet (if applicable)." } } }, "calculationProperties": { "category": { "title": "Category", "description": "Category of Incident", "calculation": ".properties | .status as $status | if ($status | IN(\"scheduled\", \"in_progress\", \"verifying\", \"completed\")) then \"maintenance\" else \"incident\" end", "type": "string", "colorized": true, "colors": { "maintenance": "bronze", "incident": "red" } } }, "relations": { "incident": { "target": "statuspageIncident", "required": true, "title": "Incident", "many": false }, "affectedComponents": { "target": "statuspageComponent", "required": false, "title": "Affected Components", "many": true } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: incident_update selector: query: "true" port: entity: mappings: identifier: .id title: .body blueprint: '"statuspageIncidentUpdate"' properties: status: .status body: .body createdAt: .created_at displayAt: .display_at deliverNotifications: .deliver_notifications wantsTwitterUpdate: .wants_twitter_update tweet_id: .tweet_id custom_tweet: .custom_tweet relations: incident: .incident_id affectedComponents: "[.affected_components[].code]" ``` --- # Komodor ![Komodor icon](/img/guides/icons/Komodor.svg)![Komodor icon](/img/guides/icons/Komodor.svg) Loading version... Port's Komodor integration allows you to model Komodor resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Services](https://api.komodor.com/api/docs/index.html#/Services/post_api_v2_services_search)[Health Monitoring](https://help.komodor.com/hc/en-us/categories/22390793120274-Health-Management) ## Overview[​](#overview "Direct link to Overview") This integration allows you to map, organize, and sync your desired Komodor resources and their metadata in Port. ### Prerequisites[​](#prerequisites "Direct link to Prerequisites") #### Generate a Komodor Api Key[​](#generate-a-komodor-api-key "Direct link to Generate a Komodor Api Key") 1. Log in to the [Komodor platfrom](https://app.komodor.com). 2. Access API Keys management page: * Click on your user profile in the top-right corner of the platform. * Select `API Keys` from the dropdown menu. 3. Generate a new API key: * Click the `Generate Key` button. * Provide a descriptive name for the API key to help you identify its purpose later (e.g., "Port.io api key"). 4. Copy the token and save it in a secure location. To read more, see the [Komodor documentation](https://help.komodor.com/hc/en-us/articles/22434108566674-Using-the-Komodor-API). API key permissions Make sure the user who creates the API key has view permissions (ideally full access) for the resources you wish to ingest into Port, since the API key inherits the user's permissions. ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from Komodor's API into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration you get after installing the Komodor integration. **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: false enableMergeEntity: true resources: - kind: komodorService selector: query: 'true' port: entity: mappings: identifier: .kind + "-" + .cluster + "-" + .namespace + "-" + .service title: .service blueprint: '"komodorService"' properties: service_id: .uid status: .status cluster_name: .cluster workload_kind: .kind namespace_name: .namespace service_name: .service komodor_link: .link + "&utmSource=port" labels: .labels last_deploy_at: .lastDeploy.endTime | todate last_deploy_status: .lastDeploy.status - kind: komodorHealthMonitoring selector: query: 'true' port: entity: mappings: identifier: .id title: .komodorUid | gsub("\\|"; "-") | sub("-+$"; "") blueprint: '"komodorHealthMonitoring"' properties: status: .status resource_identifier: .komodorUid | gsub("\\|"; "-") | sub("-+$"; "") severity: .severity supporting_data: .supportingData komodor_link: .link + "&utmSource=port" created_at: .createdAt | todate last_evaluated_at: .lastEvaluatedAt | todate check_type: .checkType workload_type: .komodorUid | split("|") | .[0] cluster_name: .komodorUid | split("|") | .[1] namespace_name: .komodorUid | split("|") | .[2] workload_name: .komodorUid | split("|") | .[3] - kind: komodorHealthMonitoring selector: query: ( .komodorUid | split("|") as $parts | (length == 4 and all($parts[]; length > 0)) ) port: entity: mappings: identifier: .id title: .komodorUid | gsub("\\|"; "-") | sub("-+$"; "") blueprint: '"komodorHealthMonitoring"' properties: {} relations: service: .komodorUid | gsub("\\|"; "-") ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") ServicesHealth Monitoring Default mappingYAML ``` - kind: komodorService selector: query: 'true' port: entity: mappings: identifier: .kind + "-" + .cluster + "-" + .namespace + "-" + .service title: .service blueprint: '"komodorService"' properties: service_id: .uid status: .status cluster_name: .cluster workload_kind: .kind namespace_name: .namespace service_name: .service komodor_link: .link + "&utmSource=port" labels: .labels last_deploy_at: .lastDeploy.endTime | todate last_deploy_status: .lastDeploy.status ``` Sample payloadJSON ``` { "data": { "services": [ { "annotations": { "checksum/config": "CHECKSUM", "deployment.kubernetes.io/revision": "1", "meta.helm.sh/release-name": "komodor-agent", "meta.helm.sh/release-namespace": "komodor" }, "cluster": "test", "kind": "Deployment", "labels": { "app.kubernetes.io/instance": "komodor-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "komodor-agent", "app.kubernetes.io/version": "X.X.X", "helm.sh/chart": "komodor-agent-X.X.X" }, "lastDeploy": { "endTime": 1740140297, "startTime": 1740140297, "status": "success" }, "link": "https://app.komodor.com/services/ACCOUNT.CLUSTER.SERVICE?workspaceId=null&referer=public-api", "namespace": "komodor", "service": "komodor-agent", "status": "healthy", "uid": "INTERNAL_KOMODOR_UID" } ] }, "meta": { "nextPage": 1, "page": 0, "pageSize": 1 } } ``` Port entityJSON ``` { "identifier": "SERVICE_ID", "title": "komodor-agent", "blueprint": "komodorService", "properties": { "serviceId": "KOMODOR_INTERNAL_ID", "status": "healthy", "cluster_name": "test", "workload_kind": "Deployment", "namespace_name": "komodor", "service_name": "komodor-agent", "link_to_komodor": "https://app.komodor.com/services/ACCOUNT_NAME.CLUSTER.SERVICE?workspaceId=null&referer=public-api", "labels": { "app.kubernetes.io/instance": "komodor-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "komodor-agent", "app.kubernetes.io/version": "X.X.X", "helm.sh/chart": "komodor-agent-X.X.X" }, "last_deploy_at": "2025-01-22T08:26:42Z", "last_deploy_status": "success" } } ``` Default mappingYAML ``` - kind: komodorHealthMonitoring selector: query: 'true' port: entity: mappings: identifier: .id title: .komodorUid | gsub("\\|"; "-") | sub("-+$"; "") blueprint: '"komodorHealthMonitoring"' properties: status: .status resource_identifier: .komodorUid | gsub("\\|"; "-") | sub("-+$"; "") severity: .severity supporting_data: .supportingData komodor_link: .link + "&utmSource=port" created_at: .createdAt | todate last_evaluated_at: .lastEvaluatedAt | todate check_type: .checkType workload_type: .komodorUid | split("|") | .[0] cluster_name: .komodorUid | split("|") | .[1] namespace_name: .komodorUid | split("|") | .[2] workload_name: .komodorUid | split("|") | .[3] - kind: komodorHealthMonitoring selector: query: ( .komodorUid | split("|") as $parts | (length == 4 and all($parts[]; length > 0)) ) port: entity: mappings: identifier: .id title: .komodorUid | gsub("\\|"; "-") | sub("-+$"; "") blueprint: '"komodorHealthMonitoring"' properties: {} relations: service: .komodorUid | gsub("\\|"; "-") ``` Sample payloadJSON ``` { "checkType": "restartingContainers", "createdAt": 1742447493, "id": "RANDOM_UID", "komodorUid": "WORKLOAD_KIND|CLUSTER_NAME|NAMESPACE_NAME|WORKLOAD_NAME", "lastEvaluatedAt": 1743292800, "link": "https://app.komodor.com/health/risks/drawer?checkCategory=workload-health&checkType=restartingContainers&violationId=78f44264-dbe1-4d0f-9096-9925f5e74ae8", "severity": "medium", "status": "open", "supportingData": { "restartingContainers": { "containers": [ { "name": "CONTAINER_NAME", "restarts": 969 } ], "restartReasons": { "breakdown": [ { "message": "Container Exited With Error - Exit Code: 1", "percent": 100, "numOccurences": 1825, "reason": "ProcessExit" } ], "additionalInfo": { "podSamples": [ { "podName": "POD_NAME_1", "restarts": 607 }, { "podName": "POD_NAME_2", "restarts": 170 }, { "podName": "POD_NAME_3", "restarts": 57 }, { "podName": "POD_NAME_4", "restarts": 53 }, { "podName": "POD_NAME_5", "restarts": 22 } ], "numRestartsOnTimeseries": 909, "numRestartsOnDB": 1825 } } } } } ``` Port entityJSON ``` { "identifier": "random-uuid", "title": "KIND|CLUSTER|NAMESPACE|NAME", "blueprint": "komodorHealthMonitoring", "properties": { "status": "open", "resource_identifier": "KIND-CLUSTER-NAMESPACE-NAME", "severity": "medium", "supporting_data": { "restartingContainers": { "containers": [ { "name": "container-name", "restarts": 969 } ], "restartReasons": { "breakdown": [ { "message": "Container Exited With Error - Exit Code: 1", "percent": 100, "numOccurences": 1825, "reason": "ProcessExit" } ], "additionalInfo": { "podSamples": [ { "podName": "POD_NAME_1", "restarts": 607 }, { "podName": "POD_NAME_2", "restarts": 170 }, { "podName": "POD_NAME_3", "restarts": 57 }, { "podName": "POD_NAME_4", "restarts": 53 }, { "podName": "POD_NAME_5", "restarts": 22 } ], "numRestartsOnTimeseries": 909, "numRestartsOnDB": 1825 } } } }, "komodor_link": "https://app.komodor.com/health/risks/drawer?checkCategory=workload-health&checkType=restartingContainers&violationId=UID&utmSource=port", "created_at": "2025-03-20T05:11:33Z", "last_evaluated_at": "2025-03-30T00:00:00Z", "check_type": "restartingContainers", "workload_type": "WORKLOAD_KIND", "cluster_name": "CLUSTER_NAME", "namespace_name": "NAMESPACE_NAME", "workload_name": "NAME" }, "relations": { "service": [ "ServiceUID" ] } } ``` ## Connect Komodor services to k8s workloads[​](#connect-komodor-services-to-k8s-workloads "Direct link to Connect Komodor services to k8s workloads") ### Prerequisites[​](#prerequisites-1 "Direct link to Prerequisites") * Install Komodor integration. * Install Port's k8s exporter integration on your cluster. * Install Komodor agent on your cluster. ### Create the relation[​](#create-the-relation "Direct link to Create the relation") 1. Navigate to the [data model](https://app.port.io/settings/data-model) page in Port. 2. Click on the Komodor Service blueprint. 3. Click on the `...` button in the top right corner, choose `Edit blueprint`, then click on the `Edit JSON` button. 4. Update the existing JSON by incorporating the following data in it. **Mapping configuration (click to expand)** ``` { "relations": { "workload": { "title": "Workload", "target": "workload", "required": false, "many": false } } } ``` ### Set up mapping configuration[​](#set-up-mapping-configuration "Direct link to Set up mapping configuration") 1. Navigate to the [connectors](https://app.port.io/settings/data-sources) page in Port. 2. Click on the Komodor integration, and scroll to the mapping section in the bottom-left corner. 3. Copy the following configuration and paste it in the editor, then click `Save & Resync`. **Mapping configuration (click to expand)** ``` - kind: komodorService selector: query: 'true' port: entity: mappings: identifier: .kind + "-" + .cluster + "-" + .namespace + "-" + .service blueprint: '"komodorService"' properties: {} relations: workload: .service + "-" + .kind + "-" + .namespace + "-" + .cluster ``` Default values This assumes that both your Komodor integration and Kubernetes exporter are using their default key and field values. If any mappings or blueprints have been modified in either integration, you may need to adjust these values accordingly. --- # Kubernetes ![Kubernetes icon](/img/guides/icons/Kubernetes.svg)![Kubernetes icon](/img/guides/icons/Kubernetes.svg) Port's Kubernetes integration allows you to model Kubernetes resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Namespace](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/)[Pod](https://kubernetes.io/docs/concepts/workloads/pods/)[ReplicaSet](https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/)[Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)[StatefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)[DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/)[Service](https://kubernetes.io/docs/concepts/services-networking/service/)[Node](https://kubernetes.io/docs/concepts/architecture/nodes/)[Custom](https://kubernetes.io/docs/reference/kubernetes-api/) ## Core support [Live eventsReal-time via K8s watch APISee supported resources](#common-use-cases) Kubernetes Exporter Port's Kubernetes exporter is open source, view the source code [**here**](https://github.com/port-labs/port-k8s-exporter). ## Common use cases[​](#common-use-cases "Direct link to Common use cases") * Visualize and organize your Kubernetes resources and their metadata in Port's software catalog. * Get real-time visibility into your cluster by automatically syncing resource changes (create, update, delete) to Port. * Create a complete map of your K8s cluster using relations between resources. * Ingest resources from common CRDs such as ArgoCD, Istio, and more. ![Kubernetes exporter mapping deployment, node, and pod data to microservices, environments, and workloads in Port](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8s-exporter-illustration.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * [Helm](https://helm.sh) must be installed. * You have your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) (Client ID and Client Secret). ## Setup[​](#setup "Direct link to Setup") 1Prerequisites2Install The prerequisites required for installation are listed below. * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. Manage K8s mapping config By default, the mapping configuration defined in your Helm values is synced to Port only during the initial installation. After that, Port's UI is the source of truth. To edit the mapping, click on the integration in your [connectors](https://app.getport.io/settings/data-sources) page and click **Save & Resync** to apply changes. If you prefer to manage your mapping declaratively via Helm or GitOps, see [Manage K8s mapping config](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/advanced/.md#manage-k8s-mapping-config). ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: v1/namespaces selector: query: .metadata.name | contains("kube-system") port: entity: mappings: - identifier: env.CLUSTER_NAME title: env.CLUSTER_NAME blueprint: '"k8s_cluster"' - kind: v1/namespaces selector: query: .metadata.name | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_namespace"' properties: creationTimestamp: .metadata.creationTimestamp labels: .metadata.labels relations: Cluster: env.CLUSTER_NAME - kind: v1/nodes selector: query: 'true' port: entity: mappings: - identifier: (.metadata.name) | (split(".")|join("_")) + "-" + env.CLUSTER_NAME title: .metadata.name + "-" + env.CLUSTER_NAME blueprint: '"k8s_node"' properties: creationTimestamp: .metadata.creationTimestamp totalCPU: .status.allocatable.cpu totalMemory: .status.allocatable.memory labels: .metadata.labels kubeletVersion: .status.nodeInfo.kubeletVersion | split("-") | .[0] ready: .status.conditions[] | select(.type == "Ready") | .status relations: Cluster: env.CLUSTER_NAME - kind: apps/v1/deployments selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-Deployment-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_workload"' properties: kind: '"Deployment"' creationTimestamp: .metadata.creationTimestamp replicas: .spec.replicas hasPrivileged: .spec.template.spec.containers | [.[].securityContext.privileged] | any hasLatest: .spec.template.spec.containers[].image | contains(":latest") hasLimits: .spec.template.spec.containers | all(has("resources") and (.resources.limits.memory and .resources.limits.cpu)) strategyConfig: .spec.strategy // {} strategy: .spec.strategy.type availableReplicas: .status.availableReplicas labels: .metadata.labels containers: (.spec.template.spec.containers | map({name, image, resources})) isHealthy: if .spec.replicas == .status.availableReplicas then "Healthy" else "Unhealthy" end relations: Namespace: .metadata.namespace + "-" + env.CLUSTER_NAME - kind: apps/v1/daemonsets selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-DaemonSet-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_workload"' properties: kind: '"DaemonSet"' creationTimestamp: .metadata.creationTimestamp replicas: .spec.replicas strategyConfig: .spec.strategy // {} availableReplicas: .status.availableReplicas hasPrivileged: .spec.template.spec.containers | [.[].securityContext.privileged] | any labels: .metadata.labels hasLatest: .spec.template.spec.containers[].image | contains(":latest") hasLimits: .spec.template.spec.containers | all(has("resources") and (.resources.limits.memory and .resources.limits.cpu)) containers: (.spec.template.spec.containers | map({name, image, resources})) isHealthy: if .spec.replicas == .status.availableReplicas then "Healthy" else "Unhealthy" end relations: Namespace: .metadata.namespace + "-" + env.CLUSTER_NAME - kind: apps/v1/statefulsets selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-StatefulSet-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_workload"' properties: kind: '"StatefulSet"' labels: .metadata.labels creationTimestamp: .metadata.creationTimestamp strategyConfig: .spec.strategy // {} replicas: .spec.replicas availableReplicas: .status.availableReplicas hasLatest: .spec.template.spec.containers[].image | contains(":latest") hasPrivileged: .spec.template.spec.containers | [.[].securityContext.privileged] | any hasLimits: .spec.template.spec.containers | all(has("resources") and (.resources.limits.memory and .resources.limits.cpu)) containers: (.spec.template.spec.containers | map({name, image, resources})) isHealthy: if .spec.replicas == .status.availableReplicas then "Healthy" else "Unhealthy" end relations: Namespace: .metadata.namespace + "-" + env.CLUSTER_NAME - kind: apps/v1/replicasets selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-ReplicaSet-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_replicaSet"' properties: creationTimestamp: .metadata.creationTimestamp replicas: .spec.replicas hasPrivileged: .spec.template.spec.containers | [.[].securityContext.privileged] | any hasLatest: .spec.template.spec.containers[].image | contains(":latest") hasLimits: .spec.template.spec.containers | all(has("resources") and (.resources.limits.memory and .resources.limits.cpu)) strategy: .spec.strategy.type availableReplicas: .status.availableReplicas labels: .metadata.labels containers: (.spec.template.spec.containers | map({name, image, resources})) isHealthy: if .spec.replicas == .status.availableReplicas then "Healthy" else "Unhealthy" end relations: replicaSetManager: .metadata.ownerReferences[0].name + "-" + .metadata.ownerReferences[0].kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME // [] workload: combinator: '"and"' rules: - operator: '"="' property: '"workload_identifier"' value: .metadata.ownerReferences[0].name + "-" + .metadata.ownerReferences[0].kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME // [] - kind: v1/pods selector: query: (.metadata.ownerReferences[0].kind == "ReplicaSet") and (.metadata.namespace | startswith("kube") | not) port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_pod"' properties: startTime: .status.startTime phase: .status.phase labels: .metadata.labels relations: replicaSet: .metadata.ownerReferences[0].name + "-" + "ReplicaSet" + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME Node: (.spec.nodeName) | (split(".")|join("_")) + "-" + env.CLUSTER_NAME - kind: v1/pods selector: query: (.metadata.ownerReferences[0].kind != "ReplicaSet") and (.metadata.namespace | startswith("kube") | not) port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_pod"' properties: startTime: .status.startTime phase: .status.phase labels: .metadata.labels relations: k8s_workload: .metadata.ownerReferences[0].name + "-" + .metadata.ownerReferences[0].kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME Node: (.spec.nodeName) | (split(".")|join("_")) + "-" + env.CLUSTER_NAME workload: combinator: '"and"' rules: - operator: '"="' property: '"workload_identifier"' value: .metadata.ownerReferences[0].name + "-" + .metadata.ownerReferences[0].kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/basic-example/.md). NamespaceNodeDeploymentDaemonSetStatefulSetReplicaSetPod Default mappingYAML ``` - kind: v1/namespaces selector: query: .metadata.name | contains("kube-system") port: entity: mappings: - identifier: env.CLUSTER_NAME title: env.CLUSTER_NAME blueprint: '"k8s_cluster"' - kind: v1/namespaces selector: query: .metadata.name | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_namespace"' properties: creationTimestamp: .metadata.creationTimestamp labels: .metadata.labels relations: Cluster: env.CLUSTER_NAME ``` Sample payloadJSON ``` { "apiVersion": "v1", "kind": "Namespace", "metadata": { "creationTimestamp": "2024-11-06T13:03:59Z", "labels": { "kubernetes.io/metadata.name": "default" }, "managedFields": [ { "apiVersion": "v1", "fieldsType": "FieldsV1", "fieldsV1": { "f:metadata": { "f:labels": { ".": {}, "f:kubernetes.io/metadata.name": {} } } }, "manager": "kube-apiserver", "operation": "Update", "time": "2024-11-06T13:03:59Z" } ], "name": "default", "resourceVersion": "39", "uid": "0aba1ce2-255d-45d1-9db0-d2089b89ffea" }, "spec": { "finalizers": [ "kubernetes" ] }, "status": { "phase": "Active" } } ``` Port entityJSON ``` { "identifier": "default-", "title": "default", "blueprint": "k8s_namespace", "properties": { "creationTimestamp": "2024-11-06T13:03:59Z", "labels": { "kubernetes.io/metadata.name": "default" } }, "relations": { "Cluster": null } } ``` Default mappingYAML ``` - kind: v1/nodes selector: query: 'true' port: entity: mappings: - identifier: (.metadata.name) | (split(".")|join("_")) + "-" + env.CLUSTER_NAME title: .metadata.name + "-" + env.CLUSTER_NAME blueprint: '"k8s_node"' properties: creationTimestamp: .metadata.creationTimestamp totalCPU: .status.allocatable.cpu totalMemory: .status.allocatable.memory labels: .metadata.labels kubeletVersion: .status.nodeInfo.kubeletVersion | split("-") | .[0] ready: .status.conditions[] | select(.type == "Ready") | .status relations: Cluster: env.CLUSTER_NAME ``` Sample payloadJSON ``` { "apiVersion": "v1", "kind": "Node", "metadata": { "annotations": { "kubeadm.alpha.kubernetes.io/cri-socket": "unix:///var/run/cri-dockerd.sock", "node.alpha.kubernetes.io/ttl": "0", "volumes.kubernetes.io/controller-managed-attach-detach": "true" }, "creationTimestamp": "2024-11-06T13:03:58Z", "labels": { "beta.kubernetes.io/arch": "arm64", "beta.kubernetes.io/os": "linux", "kubernetes.io/arch": "arm64", "kubernetes.io/hostname": "minikube", "kubernetes.io/os": "linux", "minikube.k8s.io/commit": "248d1ec5b3f9be5569977749a725f47b018078ff", "minikube.k8s.io/name": "minikube", "minikube.k8s.io/primary": "true", "minikube.k8s.io/updated_at": "2024_11_06T15_04_01_0700", "minikube.k8s.io/version": "v1.33.1", "node-role.kubernetes.io/control-plane": "", "node.kubernetes.io/exclude-from-external-load-balancers": "" }, "name": "minikube", "resourceVersion": "1327897", "uid": "e526ba59-74f2-44ec-9142-f1c27025ce07" }, "spec": { "podCIDR": "10.244.0.0/24", "podCIDRs": [ "10.244.0.0/24" ] }, "status": { "addresses": [ { "address": "192.168.49.2", "type": "InternalIP" }, { "address": "minikube", "type": "Hostname" } ], "allocatable": { "cpu": "8", "ephemeral-storage": "61202244Ki", "hugepages-1Gi": "0", "hugepages-2Mi": "0", "hugepages-32Mi": "0", "hugepages-64Ki": "0", "memory": "8037848Ki", "pods": "110" }, "capacity": { "cpu": "8", "ephemeral-storage": "61202244Ki", "hugepages-1Gi": "0", "hugepages-2Mi": "0", "hugepages-32Mi": "0", "hugepages-64Ki": "0", "memory": "8037848Ki", "pods": "110" }, "conditions": [ { "lastHeartbeatTime": "2025-01-26T17:27:26Z", "lastTransitionTime": "2025-01-19T08:03:59Z", "message": "kubelet has sufficient memory available", "reason": "KubeletHasSufficientMemory", "status": "False", "type": "MemoryPressure" }, { "lastHeartbeatTime": "2025-01-26T17:27:26Z", "lastTransitionTime": "2025-01-19T08:03:59Z", "message": "kubelet has no disk pressure", "reason": "KubeletHasNoDiskPressure", "status": "False", "type": "DiskPressure" }, { "lastHeartbeatTime": "2025-01-26T17:27:26Z", "lastTransitionTime": "2025-01-19T08:03:59Z", "message": "kubelet has sufficient PID available", "reason": "KubeletHasSufficientPID", "status": "False", "type": "PIDPressure" }, { "lastHeartbeatTime": "2025-01-26T17:27:26Z", "lastTransitionTime": "2025-01-19T08:03:59Z", "message": "kubelet is posting ready status", "reason": "KubeletReady", "status": "True", "type": "Ready" } ], "daemonEndpoints": { "kubeletEndpoint": { "Port": 10250 } }, "nodeInfo": { "architecture": "arm64", "bootID": "bed8f28a-c181-4d39-8ed5-3ae2e0c58194", "containerRuntimeVersion": "docker://26.1.1", "kernelVersion": "6.3.13-linuxkit", "kubeProxyVersion": "v1.30.0", "kubeletVersion": "v1.30.0", "machineID": "3016188b59244008a8369177d99ed73a", "operatingSystem": "linux", "osImage": "Ubuntu 22.04.4 LTS", "systemUUID": "3016188b59244008a8369177d99ed73a" } } } ``` Port entityJSON ``` { "identifier": "minikube-", "title": "minikube-", "blueprint": "k8s_node", "properties": { "creationTimestamp": "2024-11-06T13:03:58Z", "totalCPU": "8", "totalMemory": "8037848Ki", "labels": { "beta.kubernetes.io/arch": "arm64", "beta.kubernetes.io/os": "linux", "kubernetes.io/arch": "arm64", "kubernetes.io/hostname": "minikube", "kubernetes.io/os": "linux", "minikube.k8s.io/commit": "248d1ec5b3f9be5569977749a725f47b018078ff", "minikube.k8s.io/name": "minikube", "minikube.k8s.io/primary": "true", "minikube.k8s.io/updated_at": "2024_11_06T15_04_01_0700", "minikube.k8s.io/version": "v1.33.1", "node-role.kubernetes.io/control-plane": "", "node.kubernetes.io/exclude-from-external-load-balancers": "" }, "kubeletVersion": "v1.30.0", "ready": "True" }, "relations": { "Cluster": null } } ``` Default mappingYAML ``` - kind: apps/v1/deployments selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-Deployment-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_workload"' properties: kind: '"Deployment"' creationTimestamp: .metadata.creationTimestamp replicas: .spec.replicas hasPrivileged: .spec.template.spec.containers | [.[].securityContext.privileged] | any hasLatest: .spec.template.spec.containers[].image | contains(":latest") hasLimits: .spec.template.spec.containers | all(has("resources") and (.resources.limits.memory and .resources.limits.cpu)) strategyConfig: .spec.strategy // {} strategy: .spec.strategy.type availableReplicas: .status.availableReplicas labels: .metadata.labels containers: (.spec.template.spec.containers | map({name, image, resources})) isHealthy: if .spec.replicas == .status.availableReplicas then "Healthy" else "Unhealthy" end relations: Namespace: .metadata.namespace + "-" + env.CLUSTER_NAME ``` Sample payloadJSON ``` { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "annotations": { "deployment.kubernetes.io/revision": "1", "meta.helm.sh/release-name": "my-port-agent", "meta.helm.sh/release-namespace": "port-agent" }, "creationTimestamp": "2025-01-08T16:22:14Z", "generation": 1, "labels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "port-agent", "app.kubernetes.io/version": "v0.7.8", "helm.sh/chart": "port-agent-0.8.6" }, "name": "my-port-agent", "namespace": "port-agent", "resourceVersion": "1328035", "uid": "e3abe1a3-cf9b-4ccb-96d0-6fe472779026" }, "spec": { "progressDeadlineSeconds": 600, "replicas": 1, "revisionHistoryLimit": 10, "selector": { "matchLabels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/name": "port-agent" } }, "strategy": { "type": "Recreate" }, "template": { "metadata": { "labels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "port-agent", "app.kubernetes.io/version": "v0.7.8", "helm.sh/chart": "port-agent-0.8.6" } }, "spec": { "containers": [ { "env": [ { "name": "PORT_CLIENT_ID", "valueFrom": { "secretKeyRef": { "key": "PORT_CLIENT_ID", "name": "my-port-agent" } } }, { "name": "PORT_CLIENT_SECRET", "valueFrom": { "secretKeyRef": { "key": "PORT_CLIENT_SECRET", "name": "my-port-agent" } } }, { "name": "GITLAB_URL", "value": "https://gitlab.com/" }, { "name": "KAFKA_CONSUMER_AUTHENTICATION_MECHANISM", "value": "SCRAM-SHA-512" }, { "name": "KAFKA_CONSUMER_AUTO_OFFSET_RESET", "value": "largest" }, { "name": "KAFKA_CONSUMER_GROUP_ID", "value": "omri-ignore" }, { "name": "KAFKA_CONSUMER_SECURITY_PROTOCOL", "value": "SASL_SSL" }, { "name": "PORT_API_BASE_URL", "value": "https://api.port.io" }, { "name": "PORT_ORG_ID", "value": "org_Z2ykzTV308tzhfvo" }, { "name": "STREAMER_NAME", "value": "KAFKA" } ], "image": "ghcr.io/port-labs/port-agent:v0.7.8", "imagePullPolicy": "IfNotPresent", "name": "port-agent", "resources": { "limits": { "cpu": "200m", "memory": "256Mi" }, "requests": { "cpu": "100m", "memory": "128Mi" } }, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File" } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "Always", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "conditions": [ { "lastTransitionTime": "2025-01-08T16:22:14Z", "lastUpdateTime": "2025-01-08T16:22:16Z", "message": "ReplicaSet \"my-port-agent-54d9d7db6b\" has successfully progressed.", "reason": "NewReplicaSetAvailable", "status": "True", "type": "Progressing" }, { "lastTransitionTime": "2025-01-26T17:27:56Z", "lastUpdateTime": "2025-01-26T17:27:56Z", "message": "Deployment does not have minimum availability.", "reason": "MinimumReplicasUnavailable", "status": "False", "type": "Available" } ], "observedGeneration": 1, "replicas": 1, "unavailableReplicas": 1, "updatedReplicas": 1 } } ``` Port entityJSON ``` { "identifier": "my-port-agent-Deployment-port-agent-", "title": "my-port-agent", "blueprint": "k8s_workload", "properties": { "kind": "Deployment", "creationTimestamp": "2025-01-08T16:22:14Z", "replicas": 1, "hasPrivileged": false, "hasLatest": false, "hasLimits": true, "strategyConfig": { "type": "Recreate" }, "strategy": "Recreate", "availableReplicas": null, "labels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "port-agent", "app.kubernetes.io/version": "v0.7.8", "helm.sh/chart": "port-agent-0.8.6" }, "containers": [ { "name": "port-agent", "image": "ghcr.io/port-labs/port-agent:v0.7.8", "resources": { "limits": { "cpu": "200m", "memory": "256Mi" }, "requests": { "cpu": "100m", "memory": "128Mi" } } } ], "isHealthy": "Unhealthy" }, "relations": { "Namespace": "port-agent-" } } ``` Mapping configurationYAML ``` - kind: apps/v1/daemonsets selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-DaemonSet-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_workload"' properties: kind: '"DaemonSet"' creationTimestamp: .metadata.creationTimestamp replicas: .spec.replicas strategyConfig: .spec.strategy // {} availableReplicas: .status.availableReplicas hasPrivileged: .spec.template.spec.containers | [.[].securityContext.privileged] | any labels: .metadata.labels hasLatest: .spec.template.spec.containers[].image | contains(":latest") hasLimits: .spec.template.spec.containers | all(has("resources") and (.resources.limits.memory and .resources.limits.cpu)) containers: (.spec.template.spec.containers | map({name, image, resources})) isHealthy: if .spec.replicas == .status.availableReplicas then "Healthy" else "Unhealthy" end relations: Namespace: .metadata.namespace + "-" + env.CLUSTER_NAME ``` Mapping configurationYAML ``` - kind: apps/v1/statefulsets selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-StatefulSet-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_workload"' properties: kind: '"StatefulSet"' labels: .metadata.labels creationTimestamp: .metadata.creationTimestamp strategyConfig: .spec.strategy // {} replicas: .spec.replicas availableReplicas: .status.availableReplicas hasLatest: .spec.template.spec.containers[].image | contains(":latest") hasPrivileged: .spec.template.spec.containers | [.[].securityContext.privileged] | any hasLimits: .spec.template.spec.containers | all(has("resources") and (.resources.limits.memory and .resources.limits.cpu)) containers: (.spec.template.spec.containers | map({name, image, resources})) isHealthy: if .spec.replicas == .status.availableReplicas then "Healthy" else "Unhealthy" end relations: Namespace: .metadata.namespace + "-" + env.CLUSTER_NAME ``` Default mappingYAML ``` - kind: apps/v1/replicasets selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-ReplicaSet-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_replicaSet"' properties: creationTimestamp: .metadata.creationTimestamp replicas: .spec.replicas hasPrivileged: .spec.template.spec.containers | [.[].securityContext.privileged] | any hasLatest: .spec.template.spec.containers[].image | contains(":latest") hasLimits: .spec.template.spec.containers | all(has("resources") and (.resources.limits.memory and .resources.limits.cpu)) strategy: .spec.strategy.type availableReplicas: .status.availableReplicas labels: .metadata.labels containers: (.spec.template.spec.containers | map({name, image, resources})) isHealthy: if .spec.replicas == .status.availableReplicas then "Healthy" else "Unhealthy" end relations: replicaSetManager: .metadata.ownerReferences[0].name + "-" + .metadata.ownerReferences[0].kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME // [] workload: combinator: '"and"' rules: - operator: '"="' property: '"workload_identifier"' value: .metadata.ownerReferences[0].name + "-" + .metadata.ownerReferences[0].kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME // [] ``` Sample payloadJSON ``` { "apiVersion": "apps/v1", "kind": "ReplicaSet", "metadata": { "annotations": { "deployment.kubernetes.io/desired-replicas": "1", "deployment.kubernetes.io/max-replicas": "1", "deployment.kubernetes.io/revision": "1", "meta.helm.sh/release-name": "my-port-agent", "meta.helm.sh/release-namespace": "port-agent" }, "creationTimestamp": "2025-01-08T16:22:14Z", "generation": 1, "labels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "port-agent", "app.kubernetes.io/version": "v0.7.8", "helm.sh/chart": "port-agent-0.8.6", "pod-template-hash": "54d9d7db6b" }, "name": "my-port-agent-54d9d7db6b", "namespace": "port-agent", "ownerReferences": [ { "apiVersion": "apps/v1", "blockOwnerDeletion": true, "controller": true, "kind": "Deployment", "name": "my-port-agent", "uid": "e3abe1a3-cf9b-4ccb-96d0-6fe472779026" } ], "resourceVersion": "1328034", "uid": "2093b757-e7dd-445f-b2ed-af78063a4534" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/name": "port-agent", "pod-template-hash": "54d9d7db6b" } }, "template": { "metadata": { "labels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "port-agent", "app.kubernetes.io/version": "v0.7.8", "helm.sh/chart": "port-agent-0.8.6", "pod-template-hash": "54d9d7db6b" } }, "spec": { "containers": [ { "env": [ { "name": "PORT_CLIENT_ID", "valueFrom": { "secretKeyRef": { "key": "PORT_CLIENT_ID", "name": "my-port-agent" } } }, { "name": "PORT_CLIENT_SECRET", "valueFrom": { "secretKeyRef": { "key": "PORT_CLIENT_SECRET", "name": "my-port-agent" } } }, { "name": "GITLAB_URL", "value": "https://gitlab.com/" }, { "name": "KAFKA_CONSUMER_AUTHENTICATION_MECHANISM", "value": "SCRAM-SHA-512" }, { "name": "KAFKA_CONSUMER_AUTO_OFFSET_RESET", "value": "largest" }, { "name": "KAFKA_CONSUMER_GROUP_ID", "value": "omri-ignore" }, { "name": "KAFKA_CONSUMER_SECURITY_PROTOCOL", "value": "SASL_SSL" }, { "name": "PORT_API_BASE_URL", "value": "https://api.port.io" }, { "name": "PORT_ORG_ID", "value": "org_Z2ykzTV308tzhfvo" }, { "name": "STREAMER_NAME", "value": "KAFKA" } ], "image": "ghcr.io/port-labs/port-agent:v0.7.8", "imagePullPolicy": "IfNotPresent", "name": "port-agent", "resources": { "limits": { "cpu": "200m", "memory": "256Mi" }, "requests": { "cpu": "100m", "memory": "128Mi" } }, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File" } ], "dnsPolicy": "ClusterFirst", "restartPolicy": "Always", "schedulerName": "default-scheduler", "securityContext": {}, "terminationGracePeriodSeconds": 30 } } }, "status": { "fullyLabeledReplicas": 1, "observedGeneration": 1, "replicas": 1 } } ``` Port entityJSON ``` { "identifier": "my-port-agent-54d9d7db6b-ReplicaSet-port-agent-", "title": "my-port-agent-54d9d7db6b", "blueprint": "k8s_replicaSet", "properties": { "creationTimestamp": "2025-01-08T16:22:14Z", "replicas": 1, "hasPrivileged": false, "hasLatest": false, "hasLimits": true, "strategy": "", "availableReplicas": null, "labels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "port-agent", "app.kubernetes.io/version": "v0.7.8", "helm.sh/chart": "port-agent-0.8.6", "pod-template-hash": "54d9d7db6b" }, "containers": [ { "name": "port-agent", "image": "ghcr.io/port-labs/port-agent:v0.7.8", "resources": { "limits": { "cpu": "200m", "memory": "256Mi" }, "requests": { "cpu": "100m", "memory": "128Mi" } } } ], "isHealthy": "Unhealthy" }, "relations": { "replicaSetManager": "my-port-agent-Deployment-port-agent-", "workload": [] } } ``` Default mappingYAML ``` - kind: v1/pods selector: query: (.metadata.ownerReferences[0].kind == "ReplicaSet") and (.metadata.namespace | startswith("kube") | not) port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_pod"' properties: startTime: .status.startTime phase: .status.phase labels: .metadata.labels relations: replicaSet: .metadata.ownerReferences[0].name + "-" + "ReplicaSet" + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME Node: (.spec.nodeName) | (split(".")|join("_")) + "-" + env.CLUSTER_NAME - kind: v1/pods selector: query: (.metadata.ownerReferences[0].kind != "ReplicaSet") and (.metadata.namespace | startswith("kube") | not) port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"k8s_pod"' properties: startTime: .status.startTime phase: .status.phase labels: .metadata.labels relations: k8s_workload: .metadata.ownerReferences[0].name + "-" + .metadata.ownerReferences[0].kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME Node: (.spec.nodeName) | (split(".")|join("_")) + "-" + env.CLUSTER_NAME workload: combinator: '"and"' rules: - operator: '"="' property: '"workload_identifier"' value: .metadata.ownerReferences[0].name + "-" + .metadata.ownerReferences[0].kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME ``` Sample payloadJSON ``` { "apiVersion": "v1", "kind": "Pod", "metadata": { "creationTimestamp": "2025-01-08T16:22:14Z", "generateName": "my-port-agent-54d9d7db6b-", "labels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "port-agent", "app.kubernetes.io/version": "v0.7.8", "helm.sh/chart": "port-agent-0.8.6", "pod-template-hash": "54d9d7db6b" }, "name": "my-port-agent-54d9d7db6b-mkjr7", "namespace": "port-agent", "ownerReferences": [ { "apiVersion": "apps/v1", "blockOwnerDeletion": true, "controller": true, "kind": "ReplicaSet", "name": "my-port-agent-54d9d7db6b", "uid": "2093b757-e7dd-445f-b2ed-af78063a4534" } ], "resourceVersion": "1328052", "uid": "0797508b-2f12-4402-8d97-d76c713b23dd" }, "spec": { "containers": [ { "env": [ { "name": "PORT_CLIENT_ID", "valueFrom": { "secretKeyRef": { "key": "PORT_CLIENT_ID", "name": "my-port-agent" } } }, { "name": "PORT_CLIENT_SECRET", "valueFrom": { "secretKeyRef": { "key": "PORT_CLIENT_SECRET", "name": "my-port-agent" } } }, { "name": "GITLAB_URL", "value": "https://gitlab.com/" }, { "name": "KAFKA_CONSUMER_AUTHENTICATION_MECHANISM", "value": "SCRAM-SHA-512" }, { "name": "KAFKA_CONSUMER_AUTO_OFFSET_RESET", "value": "largest" }, { "name": "KAFKA_CONSUMER_GROUP_ID", "value": "omri-ignore" }, { "name": "KAFKA_CONSUMER_SECURITY_PROTOCOL", "value": "SASL_SSL" }, { "name": "PORT_API_BASE_URL", "value": "https://api.port.io" }, { "name": "PORT_ORG_ID", "value": "org_Z2ykzTV308tzhfvo" }, { "name": "STREAMER_NAME", "value": "KAFKA" } ], "image": "ghcr.io/port-labs/port-agent:v0.7.8", "imagePullPolicy": "IfNotPresent", "name": "port-agent", "resources": { "limits": { "cpu": "200m", "memory": "256Mi" }, "requests": { "cpu": "100m", "memory": "128Mi" } }, "terminationMessagePath": "/dev/termination-log", "terminationMessagePolicy": "File", "volumeMounts": [ { "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", "name": "kube-api-access-5wglj", "readOnly": true } ] } ], "dnsPolicy": "ClusterFirst", "enableServiceLinks": true, "nodeName": "minikube", "preemptionPolicy": "PreemptLowerPriority", "priority": 0, "restartPolicy": "Always", "schedulerName": "default-scheduler", "securityContext": {}, "serviceAccount": "default", "serviceAccountName": "default", "terminationGracePeriodSeconds": 30, "tolerations": [ { "effect": "NoExecute", "key": "node.kubernetes.io/not-ready", "operator": "Exists", "tolerationSeconds": 300 }, { "effect": "NoExecute", "key": "node.kubernetes.io/unreachable", "operator": "Exists", "tolerationSeconds": 300 } ], "volumes": [ { "name": "kube-api-access-5wglj", "projected": { "defaultMode": 420, "sources": [ { "serviceAccountToken": { "expirationSeconds": 3607, "path": "token" } }, { "configMap": { "items": [ { "key": "ca.crt", "path": "ca.crt" } ], "name": "kube-root-ca.crt" } }, { "downwardAPI": { "items": [ { "fieldRef": { "apiVersion": "v1", "fieldPath": "metadata.namespace" }, "path": "namespace" } ] } } ] } } ] }, "status": { "conditions": [ { "lastTransitionTime": "2025-01-26T17:27:29Z", "status": "True", "type": "PodReadyToStartContainers" }, { "lastTransitionTime": "2025-01-08T16:22:14Z", "status": "True", "type": "Initialized" }, { "lastTransitionTime": "2025-01-26T17:27:56Z", "message": "containers with unready status: [port-agent]", "reason": "ContainersNotReady", "status": "False", "type": "Ready" }, { "lastTransitionTime": "2025-01-26T17:27:56Z", "message": "containers with unready status: [port-agent]", "reason": "ContainersNotReady", "status": "False", "type": "ContainersReady" }, { "lastTransitionTime": "2025-01-08T16:22:14Z", "status": "True", "type": "PodScheduled" } ], "containerStatuses": [ { "containerID": "docker://b6009181cc2c5f84c15fe036f103fe8e662276a7e96d6225edfa28e292c807b0", "image": "ghcr.io/port-labs/port-agent:v0.7.8", "imageID": "docker-pullable://ghcr.io/port-labs/port-agent@sha256:bbf54c3d8912b84c57f6b18801815219e589508c1252f104808fde5164c06c2e", "lastState": { "terminated": { "containerID": "docker://b6009181cc2c5f84c15fe036f103fe8e662276a7e96d6225edfa28e292c807b0", "exitCode": 1, "finishedAt": "2025-01-26T17:27:55Z", "reason": "Error", "startedAt": "2025-01-26T17:27:48Z" } }, "name": "port-agent", "ready": false, "restartCount": 2, "started": false, "state": { "waiting": { "message": "back-off 20s restarting failed container=port-agent pod=my-port-agent-54d9d7db6b-mkjr7_port-agent(0797508b-2f12-4402-8d97-d76c713b23dd)", "reason": "CrashLoopBackOff" } } } ], "hostIP": "192.168.49.2", "hostIPs": [ { "ip": "192.168.49.2" } ], "phase": "Running", "podIP": "10.244.0.19", "podIPs": [ { "ip": "10.244.0.19" } ], "qosClass": "Burstable", "startTime": "2025-01-08T16:22:14Z" } } ``` Port entityJSON ``` { "identifier": "my-port-agent-54d9d7db6b-mkjr7-port-agent-", "title": "my-port-agent-54d9d7db6b-mkjr7", "blueprint": "k8s_pod", "properties": { "startTime": "2025-01-08T16:22:14Z", "phase": "Running", "labels": { "app.kubernetes.io/instance": "my-port-agent", "app.kubernetes.io/managed-by": "Helm", "app.kubernetes.io/name": "port-agent", "app.kubernetes.io/version": "v0.7.8", "helm.sh/chart": "port-agent-0.8.6", "pod-template-hash": "54d9d7db6b" }, "containers": [ { "image": "ghcr.io/port-labs/port-agent:v0.7.8", "resources": { "limits": { "cpu": "200m", "memory": "256Mi" }, "requests": { "cpu": "100m", "memory": "128Mi" } }, "containerID": "docker://b6009181cc2c5f84c15fe036f103fe8e662276a7e96d6225edfa28e292c807b0", "imageID": "docker-pullable://ghcr.io/port-labs/port-agent@sha256:bbf54c3d8912b84c57f6b18801815219e589508c1252f104808fde5164c06c2e", "lastState": { "terminated": { "containerID": "docker://b6009181cc2c5f84c15fe036f103fe8e662276a7e96d6225edfa28e292c807b0", "exitCode": 1, "finishedAt": "2025-01-26T17:27:55Z", "reason": "Error", "startedAt": "2025-01-26T17:27:48Z" } }, "name": "port-agent", "ready": false, "restartCount": 2, "started": false, "state": { "waiting": { "message": "back-off 20s restarting failed container=port-agent pod=my-port-agent-54d9d7db6b-mkjr7_port-agent(0797508b-2f12-4402-8d97-d76c713b23dd)", "reason": "CrashLoopBackOff" } } } ] }, "relations": { "replicaSet": "my-port-agent-54d9d7db6b-ReplicaSet-port-agent-", "Node": "minikube-" } } ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Capabilities[​](#capabilities "Direct link to Capabilities") ### Extract, Transform, Load (ETL)[​](#extract-transform-load-etl "Direct link to Extract, Transform, Load (ETL)") Port's Kubernetes exporter performs ETL on data from K8s into your desired software catalog data model: #### How it works[​](#how-it-works "Direct link to How it works") The exporter is deployed using a Helm chart installed on the cluster. Once it is set up, it continues to sync changes, meaning that all changes, deletions or additions are accurately and automatically reflected in Port. The helm chart uses a YAML configuration stored in the integration within your Portal. This configuration describes the ETL process responsible for loading data into the developer portal. The approach reflects a golden middle between an overly opinionated K8s visualization that might not work for everyone and a too-broad approach that could introduce unneeded complexity into the developer portal. Here is an example snippet from the integration configuration which demonstrates the ETL process for getting `ReplicaSet` data from the cluster and into the software catalog: ![Kubernetes exporter YAML showing resource extraction and Port entity transformation and loading](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8s-etl.png) ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found on the Kubernetes basic [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/basic-example/.md). This section includes sample response data from Kubernetes. In addition, it includes the entity created from the resync event based on the Ocean configuration provided in the previous section. ## Templates[​](#templates "Direct link to Templates") Port provides several pre-built templates to help you quickly get started with common Kubernetes use cases. These templates include pre-configured blueprints, mapping configurations, and setup instructions. Check out the [templates page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/.md) to find the template that best fits your needs. ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=Kubernetes). ## Advanced[​](#advanced "Direct link to Advanced") Refer to the [advanced](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/advanced/.md) page for advanced use cases and outputs. --- # Advanced The K8s exporter supports additional flags and the option to provide additional configuration sources, making it easier to configure its behavior to your liking. ## Required configuration[​](#required-configuration "Direct link to Required configuration") The following parameters are required with every K8s exporter installation/upgrade: | Parameter | Description | | --------------------------------- | ------------------ | | `secret.secrets.portClientId` | Port Client ID | | `secret.secrets.portClientSecret` | Port Client Secret | ## Advanced installation parameters[​](#advanced-installation-parameters "Direct link to Advanced installation parameters") The following advanced configuration parameters are available: * Resync Interval * State Key * Overwrite Configuration * Logging Level * Metrics * Bulk Sync * Event Listener Type * CRDs to Discover * JQ Configuration The `resyncInterval` parameter specifies the interval in minutes to send a repeated sync event for all known existing objects (in addition to new cluster events). * **Default value**: `0` (re-sync disabled) * **Use case**: Re-sync every X minutes. This parameter is useful when reporting entities with relations inside your cluster in instances where an entity is reported before its related target has been created in Port. The initial sync will fail, but later when the target entity is available, the entity creation will succeed. The `stateKey` parameter specifies a unique state key per K8s exporter installation. Enables deletion of stale Port entities that had been created by the exporter, and shouldn't be synced (anymore) according to your existing exporter app configuration. The exporter will check for pending deletions during pod initialization, and also respond to deletion events in the cluster. * **Default value**: `""`. * When empty, a `UUID` will be automatically generated and kept in the ConfigMap. Changing the state key will cause the existing exporter to lose track of entities it reported previously from the cluster, and will therefore not delete them from Port. * **Use case**: Deletion of stale Port entities. For example: * Removal of entire resource (like `pods`) from the exporter app config, will also remove them from the software catalog. * Modification of an entity's identifier will cause the stale entity to be removed and created again with the correct identifier. The `overwriteConfigurationOnRestart` parameter controls how the exporter's [mapping configuration](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) is managed after the initial installation. * **Default value**: `false` When set to `false` (the default), the mapping configuration defined in your Helm chart values is synced to Port **only on the first installation**. After that, the Port UI becomes the source of truth for the integration's mapping. Any subsequent changes to the mapping in your Helm values will **not** be applied. You have to update the mapping directly in the [connectors](https://app.port.io/settings/data-sources) page in Port for the changes to take effect. When set to `true`, the Helm chart becomes the source of truth. On every exporter restart (including pod restarts and Helm upgrades), the exporter pushes its local configuration to Port, overwriting whatever is currently stored there. This is useful if you want to manage your mapping configuration declaratively (e.g. via GitOps). UI changes will be overwritten When `overwriteConfigurationOnRestart` is set to `true`, any changes you make to the mapping via the Port UI will be overwritten the next time the exporter restarts. Make sure to apply all mapping changes through your Helm values or Git repository instead. **Example** — enabling overwrite via Helm: ``` helm upgrade --install k8s-exporter port-labs/port-k8s-exporter \ --create-namespace --namespace port-k8s-exporter \ --set secret.secrets.portClientId="YOUR_PORT_CLIENT_ID" \ --set secret.secrets.portClientSecret="YOUR_PORT_CLIENT_SECRET" \ --set stateKey="k8s-exporter" \ --set overwriteConfigurationOnRestart=true \ --set-file configMap.config=my-config.yaml ``` Where `my-config.yaml` contains your full mapping configuration, including `resources`, `crdsToDiscover`, and any other configuration keys. The K8S exporter provides support for multiple event listeners. The event listener is used to receive events and resync requests from Port and forward them to the exporter. multiple exporter instances The event listeners that are currently available do not support multiple instances of the same exporter By configuring an event listener the integration will listen to and react to the following events sent from Port: * **Configuration update** - the integration will use the data of the new configuration to perform a resync of information from the k8s cluster * **Resync request** - the integration will perform a resync of data from the k8s cluster to Port based on the existing configuration The following event listener types are supported: * **POLLING** - the integration will automatically query Port for updates in the integration configuration and perform a resync if changes are detected. * **KAFKA** - the integration will consume incoming resync requests from your dedicated Kafka topic, provisioned to you by Port Available event listeners configuration parameters can be found [here](https://github.com/port-labs/helm-charts/blob/main/charts/port-k8s-exporter/README.md#chart) resync If a resync event is received by your integration while it is actively performing a resync, the currently running resync will be aborted and a new resync process will start. If a new resync trigger consistently aborts a running resync, it means that your integration never finishes a complete resync process (which means some information from the cluster might never appear in Port). The `loggingLevel` parameter is used to control the level of logs in the K8s exporter's pod. * **Default value**: `info` - show all info and error logs, including info logs of successful updates. * **Use case**: Set the value to `error` if you want to include error logs only (and exclude info logs). The `metricsEnabled` parameter is used to control if the k8s exporter would expose a `/metrics` endpoint compatible with OTLP. * **Default value**: `true` The `metricsPort` parameter is used to control the port number of the metrics server. * **Default value**: `9090` * **Use case**: Set the value to a different port if you do not wish to expose the metrics server on `9090`. The `bulkSyncMaxPayloadBytes` parameter is used to control the size (in bytes) of the bulk upserts to Port. * **Default value**: `1048576` The `bulkSyncMaxEntitiesPerBatch` parameter is used to control the number of entities to be sent as part of a bulk upsert to Port. * **Default value**: `20` The `bulkSyncBatchTimeoutSeconds` parameter is used to control the time (in seconds) to wait until flushing a bulk upsert to Port (even if the maximum number/size of entities has not been reached). * **Default value**: `5` The `crdsToDiscover` parameter is used to specify a filter for the CRDs that the K8s exporter should discover and export to Port, without the need to create a mapping and the blueprint manually. For more information about Kubernetes exporter configuration, see the [Kubernetes integration documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md). * **Default value**: `""` (no filter) The K8s exporter supports configuration options to control access to environment variables within JQ queries used in resource mappings. ### `allowAllEnvironmentVariablesInJQ` The `allowAllEnvironmentVariablesInJQ` parameter controls whether all environment variables are accessible in JQ queries. * **Default value**: `true` (all environment variables are accessible within JQ queries) * **Security implications**: When set to `true`, JQ queries in your resource mappings can access any environment variable available to the exporter pod, including sensitive information like API keys, passwords, and other secrets (if those are mapped to the exporter pod as environment variables). Security Risk Setting `allowAllEnvironmentVariablesInJQ` to `true` can expose sensitive environment variables to JQ queries. This includes: * Port credentials (`PORT_CLIENT_ID`, `PORT_CLIENT_SECRET`) * Kubernetes service account tokens. * Any other environment variables injected into the pod. * Secrets mounted as environment variables. Due to the potential security implication, if you have a need to limit the exposure of environment variables in the exporter's JQ, please set this parameter to `false` and explicitly specify the variables that need to be accessed using JQ through the `allowedEnvironmentVariablesInJQ` parameter. ### `allowedEnvironmentVariablesInJQ` The `allowedEnvironmentVariablesInJQ` parameter specifies which environment variables are allowed in JQ queries when `allowAllEnvironmentVariablesInJQ` is set to `false`. This parameter accepts a list of JQ expressions that evaluate to environment variable names or patterns. Each entry in the list is a JQ expression that should return: * A specific environment variable name (e.g., `"CLUSTER_NAME"`) * A pattern for matching multiple variables (e.g., `"^CLUSTER_"` to match all cluster-related environment variables) * An array of environment variable names or patterns. * **Default value**: `^PORT_, CLUSTER_NAME` * **Use case**: Restrict access to only specific, safe environment variables in JQ queries for enhanced security. Use JQ expressions to dynamically determine which environment variables should be accessible. ### Configuration Example ``` allowAllEnvironmentVariablesInJQ: false allowedEnvironmentVariablesInJQ: - ^CLUSTER_ - AWS_REGION - AWS_ACCOUNT_ID resources: - kind: v1/namespaces ... ``` This configuration allows: * All environment variables starting with `CLUSTER_` (e.g., `CLUSTER_NAME`, `CLUSTER_ID`) * Specific variables: `AWS_REGION` & `AWS_ACCOUNT_ID` ## Security Configuration[​](#security-configuration "Direct link to Security Configuration") The following security parameters can be modified to give the K8s exporter more granular access to your cluster: | Parameter | Description | Default | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `clusterRole.apiGroups` | The API groups that the K8s Exporter can access. Make sure to grant access to the relevant API groups, with respect to the resources that you've configured in the resource mapping | `{'*'}` | | `clusterRole.resources` | The resources that the K8s Exporter can access. Make sure to grant access to the relevant resources, with respect to the resources that you've configured in the resource mapping | `{'*'}` | | `serviceAccount.create` | Whether to create the default ServiceAccount, ClusterRole and ClusterRoleBinding resources. | `true` | | `serviceAccount.name` | The name of the custom ServiceAccount resource to use, relevant only when `serviceAccount.create` is set to `false` | | ## Overriding configurations[​](#overriding-configurations "Direct link to Overriding configurations") When installing the K8s exporter, it is possible to override default values in the `helm upgrade` command: By using the `--set` flag, you can override specific exporter configuration parameters during exporter installation/upgrade: ``` helm upgrade --install k8s-exporter port-labs/port-k8s-exporter \ --create-namespace --namespace port-k8s-exporter \ --set secret.secrets.portClientId="YOUR_PORT_CLIENT_ID" \ --set secret.secrets.portClientSecret="YOUR_PORT_CLIENT_SECRET" \ --set stateKey="k8s-exporter" \ --set eventListenerType="KAFKA" \ --set extraEnv=[{"name":"CLUSTER_NAME","value":"my-cluster"}] ``` For example, to set the parameters from the [security configuration](#security-configuration) section: ``` --set clusterRole.apiGroups="{argoproj.io,'',apps}" \ --set clusterRole.resources="{rollouts,pods,replicasets}" ``` ## Manage K8s mapping config[​](#manage-k8s-mapping-config "Direct link to Manage K8s mapping config") The K8s exporter's [mapping configuration](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) (the `resources` block, `crdsToDiscover`, etc.) can be managed in two ways: through the **Port UI** or through your **Helm chart values**. The `overwriteConfigurationOnRestart` parameter controls which approach is used. ### Port UI (default) By default (`overwriteConfigurationOnRestart: false`), the mapping defined in your Helm values is synced to Port only during the **initial installation**. After that: * The [connectors](https://app.port.io/settings/data-sources) page in Port is the source of truth. * You can edit the mapping in the Port UI and click **Save & Resync** to apply changes. * Any mapping changes in your Helm values will be **ignored** on subsequent upgrades or restarts. This is the recommended approach if you prefer to iterate on your mapping through the Port UI. ### Helm chart / GitOps If you want to manage your mapping declaratively (e.g. storing it in Git alongside your Helm values), set `overwriteConfigurationOnRestart: true`. With this approach: * The Helm chart is the source of truth for the mapping configuration. * On every exporter restart, the local configuration is pushed to Port, overwriting any changes made in the UI. * All mapping changes should be made in your Helm values file and applied via `helm upgrade`. To use this approach, provide your mapping configuration via the `configMap.config` value: **Example values.yaml (click to expand)** values.yaml ``` overwriteConfigurationOnRestart: true configMap: config: | crdsToDiscover: ".metadata.ownerReferences[0].kind == \"CompositeResourceDefinition\"" resources: - kind: apps/v1/deployments selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-Deployment-" + .metadata.namespace title: .metadata.name blueprint: '"k8s_workload"' properties: kind: '"Deployment"' creationTimestamp: .metadata.creationTimestamp ``` Switching between approaches If you have been managing your mapping in the Port UI and want to switch to a GitOps approach, first copy the current mapping from the [connectors](https://app.port.io/settings/data-sources) page into your Helm values file. Then set `overwriteConfigurationOnRestart: true` and run `helm upgrade`. This ensures you don't lose any mapping changes you previously made in the UI. ## All configuration parameters[​](#all-configuration-parameters "Direct link to All configuration parameters") * A complete list of configuration parameters available when using the helm chart is available [here](https://github.com/port-labs/helm-charts/tree/main/charts/port-k8s-exporter#chart). * An example skeleton `values.yml` file is available [here](https://github.com/port-labs/helm-charts/blob/main/charts/port-k8s-exporter/values.yaml). ## Extra environment variables[​](#extra-environment-variables "Direct link to Extra environment variables") To pass extra environment variables to the exporter's runtime, you can use the Helm chart provided with the installation. You can do this in one of two ways: 1. Using Helm's `--set` flag: ``` helm upgrade --install port-labs/port-k8s-exporter \ # Standard installation flags # ... --set "extraEnv[0].name"=HTTP_PROXY \ --set "extraEnv[0].value"=http://my-proxy.com:1111 ``` 2. The Helm `values.yaml` file: ``` # The rest of the configuration # ... extraEnvs: - name: HTTP_PROXY value: http://my-proxy.com:1111 ``` ### Proxy Configuration[​](#proxy-configuration "Direct link to Proxy Configuration") #### `HTTP_PROXY` & `HTTPS_PROXY`[​](#http_proxy--https_proxy "Direct link to http_proxy--https_proxy") `HTTP_PROXY` and `HTTPS_PROXY` are environment variables used to specify a proxy server for handling HTTP or HTTPS, respectively. The values assigned to these settings should be the URL of the proxy server. For example: ``` HTTP_PROXY=http://my-proxy.com:1111 HTTPS_PROXY=http://my-proxy.com:2222 ``` ### `NO_PROXY`[​](#no_proxy "Direct link to no_proxy") `NO_PROXY` allows blacklisting certain addresses from being handled through a proxy. This variable accepts a comma-seperated list of hostnames or urls. For example: ``` NO_PROXY=http://127.0.0.1,google.com ``` ## Advanced resource mapping configuration[​](#advanced-resource-mapping-configuration "Direct link to Advanced resource mapping configuration") * Delete Dependents * Create Missing Related Entities The `deleteDependents` parameter is used to enable deletion of dependent Port entities. This is useful when you have two blueprints with a required relation, and the target entity in the relation should be deleted. In this scenario, the delete operation will fail if this flag is set to `false` if the flag is set to `true`, the source entity will be deleted as well. * **Default value**: `false` (disabled) * **Use case**: Deletion of dependent Port entities. Must be enabled if you want to delete a target entity (and its source entities) when the entity's blueprint has required relations. The `createMissingRelatedEntities` parameter is used to enable the creation of missing related Port entities. * **Default value**: `true` to allow the Kubernetes app to create barebones related entities, in case those related entities do not exist in the software catalog. * **Use case**: use `false` if you do not want this default behavior (do not create missing related entities). --- # Basic example ## Basic Pod and ReplicaSet solution[​](#basic-pod-and-replicaset-solution "Direct link to Basic Pod and ReplicaSet solution") The following example demonstrates how to export your Kubernetes `Replica Sets` and `Pods` to Port, you may use the following Port blueprint definitions, and exporter configuration: * **Deployment config** - will represent replica sets from the K8s cluster. * **Deployed service pod** - will represent pods from the K8s cluster. **Deployment config blueprint (click to expand)** Create in Port ``` { "identifier": "deploymentConfig", "title": "Deployment Config", "icon": "Cluster", "schema": { "properties": { "newRelicUrl": { "type": "string", "format": "url", "title": "New Relic", "description": "Link to the new relic dashboard of the service", "default": "https://newrelic.com" }, "sentryUrl": { "type": "string", "format": "url", "title": "Sentry URL", "description": "Link to the new sentry dashboard of the service", "default": "https://sentry.io/" }, "prometheusUrl": { "type": "string", "format": "url", "title": "Prometheus URL", "default": "https://prometheus.io" }, "locked": { "type": "boolean", "title": "Locked", "default": false, "description": "Are deployments currently allowed for this configuration", "icon": "Lock" }, "creationTimestamp": { "type": "string", "title": "Creation Timestamp", "format": "date-time" }, "annotations": { "type": "object", "title": "Annotations" }, "status": { "type": "object", "title": "Status" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Deployed service pod blueprint (click to expand)** Create in Port ``` { "identifier": "deployedServicePod", "title": "Deployed Service Pod", "icon": "Cluster", "schema": { "properties": { "startTime": { "type": "string", "title": "Start Time", "format": "date-time" }, "phase": { "type": "string", "title": "Phase", "enum": ["Pending", "Running", "Succeeded", "Failed", "Unknown"], "enumColors": { "Pending": "yellow", "Running": "blue", "Succeeded": "green", "Failed": "red", "Unknown": "darkGray" } }, "labels": { "type": "object", "title": "Labels" }, "containers": { "title": "Containers", "type": "array" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "deploymentConfig": { "target": "deploymentConfig", "required": false, "many": false } } } ``` **Port K8s exporter configuration (click to expand)** ``` resources: # List of K8s resources to list, watch, and export to Port. - kind: apps/v1/replicasets # group/version/resource (G/V/R) format selector: query: .metadata.namespace | startswith("kube") | not # JQ boolean query. If evaluated to false - skip syncing the object. port: entity: mappings: # Mappings between one K8s object to one or many Port Entities. Each value is a JQ query. - identifier: .metadata.name title: .metadata.name blueprint: '"deploymentConfig"' properties: creationTimestamp: .metadata.creationTimestamp annotations: .metadata.annotations status: .status - kind: v1/pods selector: query: .metadata.namespace | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name title: .metadata.name blueprint: '"deployedServicePod"' properties: startTime: .status.startTime phase: .status.phase labels: .metadata.labels containers: (.spec.containers | map({image, resources})) + .status.containerStatuses | group_by(.image) | map(add) relations: deploymentConfig: .metadata.ownerReferences[0].name ``` After creating the blueprints, open your integration in the [connectors](https://app.port.io/settings/data-sources) page, apply the provided configuration and click on the `Save & Resync` button. Done! The exporter will begin creating and updating objects from your Kubernetes cluster as Port entities shortly. For instance, you can see a `Deployment Config` and its `Pods` in a single Port entity page: ![Port Deployment Config entity page showing a related deployed service pod](/img/integrations/k8s-exporter/DeploymentConfigAndPods.png)

Partial property mapping The Kubernetes exporter was instructed to fill in only some properties in a `Deployment Config` Entity. By its nature, it will keep the values of other properties untouched. And you can look for the respective audit logs with an indication of the Kubernetes exporter as the source: ![Port audit log showing successful create and update events from the Kubernetes exporter](/img/integrations/k8s-exporter/AuditLog.png) ## Map your complete K8s ecosystem[​](#map-your-complete-k8s-ecosystem "Direct link to Map your complete K8s ecosystem") To learn how you can visualize a complete K8s cluster, including **nodes**, **namespaces**, **cluster roles**, **deployments**, **pods**, and more - check out our Kubernetes mapping [complete use case](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/full-kubernetes-exporter/.md) --- # Custom CRDs In addition to built-in Kubernetes API resources, it is possible to use Port's Kubernetes exporter to export and map [CRDs](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) from your Kubernetes cluster. We created several pre-built Port K8s [exporter templates](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/.md), which use CRD exporting, for a quick start when using common 3rd party k8s tools. It is also possible to write custom CRD export templates for any additional CRDs you might use in your Kubernetes clusters. To obtain all the resource types you can query in your cluster, you can run: ``` $ kubectl api-resources ``` For example, to figure out how to map your ArgoCD resources, you can run: ``` $ kubectl api-resources | grep -i argo applications app,apps argoproj.io/v1alpha1 true Application applicationsets appset,appsets argoproj.io/v1alpha1 true ApplicationSet appprojects appproj,appprojs argoproj.io/v1alpha1 true AppProject ``` Then, to map ArgoCD `applications`, add the matching resource kind to your exporter configuration: ``` - kind: argoproj.io/v1alpha1/applications identifier: .metadata.name blueprint: '"argoApp"' properties: ... ``` --- # Port Entity CRD [Port's K8s exporter](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) allows exporting data from any resource in your Kubernetes clusters, including [Custom Resource Definitions](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)(CRDs). To take advantage of the flexibility of Port's K8s exporter, Port provides additional CRDs which make it possible to use K8s resource definitions as a source of [entities](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#creating-entities) in your software catalog. Port CRDs on GitHub All CRDs provided by Port can be found [here.](https://github.com/port-labs/port-crds) ## Port CRDs[​](#port-crds "Direct link to Port CRDs") A Port entity can represent any kind of data in your infrastructure, from nodes to pods to non-Kubernetes related entities such as repositories or microservices. To achieve this level of abstraction, 2 CRDs are provided: ## Namespace scoped entity CRD - `port.io/v1/Entity`[​](#namespace-scoped-entity-crd---portiov1entity "Direct link to namespace-scoped-entity-crd---portiov1entity") **Entity CRD (click to expand)** ``` apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: entities.getport.io spec: group: port.io versions: - name: v1 served: true storage: true additionalPrinterColumns: - name: Blueprint ID type: string jsonPath: .spec.blueprint - name: Entity ID type: string jsonPath: .spec.identifier - name: Properties type: string jsonPath: .spec.properties - name: Relations type: string jsonPath: .spec.relations schema: openAPIV3Schema: type: object properties: spec: type: object properties: blueprint: type: string identifier: type: string properties: type: object x-kubernetes-preserve-unknown-fields: true relations: type: object x-kubernetes-preserve-unknown-fields: true required: - blueprint - identifier scope: Namespaced names: plural: entities singular: entity kind: Entity shortNames: - ent ``` ## Cluster scoped entity CRD - `port.io/v1/ClusterEntity`[​](#cluster-scoped-entity-crd---portiov1clusterentity "Direct link to cluster-scoped-entity-crd---portiov1clusterentity") **Cluster entity CRD (click to expand)** ``` apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: clusterentities.getport.io spec: group: port.io versions: - name: v1 served: true storage: true additionalPrinterColumns: - name: Blueprint ID type: string jsonPath: .spec.blueprint - name: Entity ID type: string jsonPath: .spec.identifier - name: Properties type: string jsonPath: .spec.properties - name: Relations type: string jsonPath: .spec.relations schema: openAPIV3Schema: type: object properties: spec: type: object properties: blueprint: type: string identifier: type: string properties: type: object x-kubernetes-preserve-unknown-fields: true relations: type: object x-kubernetes-preserve-unknown-fields: true required: - blueprint - identifier scope: Cluster names: plural: clusterentities singular: clusterentity kind: ClusterEntity shortNames: - cent ``` ## Port CRDs Structure[​](#port-crds-structure "Direct link to Port CRDs Structure") Port CRDs provide four key attributes: * `Blueprint ID` **Required**: The [blueprint](/context-lake/data-model/setup-blueprint/overview/.md#what-is-a-blueprint) identifier (string) of the entity you wish to map. * `Entity ID` **Required**: The [entity](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#creating-entities) identifier (string) of the entity you wish to map. * `Properties` **Optional**: The [properties](/context-lake/data-model/setup-blueprint/properties/overview/.md) field (object) holds the properties data of the entity you want to map. * `Relations` **Optional**: The [relations](/context-lake/data-model/setup-blueprint/relate-blueprints/.md) field (object) holds the relations data of the entity you want to map. **CRD examples (click to expand)** ``` # Namespaced Port Entity CRD example apiVersion: port.io/v1 kind: Entity metadata: name: example-entity-resource namespace: example-namespace spec: blueprint: blueprint-identifier identifier: entity-identifier properties: myStringProp: string myArrayProp: - string_1 - string_2 - string_3 myUrlProp: https://test-url.com relations: mySingleRelation: relation-target-id myManyRelations: - relation-target-id-1 - relation-target-id-2 # Namespaced Port Cluster Entity CRD example apiVersion: port.io/v1 kind: ClusterEntity metadata: name: example-cluster-entity-resource spec: blueprint: blueprint-identifier identifier: entity-identifier properties: myStringProp: string myArrayProp: - string_1 - string_2 - string_3 myUrlProp: https://test-url.com relations: mySingleRelation: relation-target-id myManyRelation: - relation-target-id-1 - relation-target-id-2 ``` ## Deploying Port's CRDs[​](#deploying-ports-crds "Direct link to Deploying Port's CRDs") To deploy Port's CRDs in your K8s cluster, run the following commands: ``` # Run this to install Port's namespace-scoped CRD kubectl apply -f https://raw.githubusercontent.com/port-labs/port-crds/main/port-entity-crd-namespace.yaml # Run this to install Port's cluster-scoped CRD kubectl apply -f https://raw.githubusercontent.com/port-labs/port-crds/main/port-entity-crd-cluster.yaml ``` ## Exporting Port's custom resources[​](#exporting-ports-custom-resources "Direct link to Exporting Port's custom resources") To export the Port entity CRDs using Port's K8s exporter, you will need to add a new resources to your exporter configuration. This mapping configuration will match the blueprint data model you defined in your software catalog. To learn how to use Port CRDs to fit your needs, you will follow an example. It will give you a general understanding of how to map any data you would like. ### Example - Mapping a microservice using Port CRDs[​](#example---mapping-a-microservice-using-port-crds "Direct link to Example - Mapping a microservice using Port CRDs") The goal for this example is to map a microservice using Port's CRD and Port's K8s exporter. For this example, you will map a microservice as a Port entity. Prerequisites Before getting started: * Prepare your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials). * Be familiar with [Port's K8s exporter](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) and configuration. * Make sure you are connected to a K8s cluster using `kubectl`. 1. **Deploy the Port CRD** - follow the [deployment step](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/port-crd/.md#deploying-ports-crds) to deploy the Port CRD. You will only need the cluster-scoped entity CRD. 2. **Creating the blueprint** - You will begin by defining the blueprint which will represent a microservice in your software catalog. Create the following blueprint in your Port environment: Create in Port ``` { "identifier": "microservice", "title": "Microservice", "icon": "Microservice", "description": "This blueprint represents a microservice", "schema": { "properties": { "description": { "title": "Description", "description": "A short description for this microservice", "type": "string" }, "onCall": { "title": "On-Call", "description": "Who the on-call for this microservice is", "type": "string" }, "slackChannel": { "title": "Slack Channel", "description": "The URL of this microservice's Slack channel", "format": "url", "type": "string" }, "datadogUrl": { "title": "Datadog URL", "description": "The URL of this microservice's DataDog dashboard", "format": "url", "type": "string" }, "language": { "type": "string", "description": "The language this microservice is written in", "title": "Language" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` 3. **Create a Port entity custom resource in your cluster** - create an Entity CR which will represent a microservice, using the scheme defined in your blueprint: 1. Create the following file as `port-entity.yaml`: ``` # Namespaces Port Entity CRD example apiVersion: port.io/v1 kind: ClusterEntity metadata: name: my-microservice-entity spec: blueprint: microservice identifier: my-awesome-microservice properties: description: This entity represents my awesome microservice onCall: some@email.com slackChannel: https://domain.slack.com/archives/12345678 datadogUrl: https://www.datadoghq.com/ language: typescript ``` 4. Apply the CRD manifest to your cluster: ``` kubectl apply -f port-entity.yaml ``` 4. **Create a mapping configuration for the K8s exporter** - create (or add to an existing) the following exporter configuration to map this CRD using Port's k8s exporter: 1. Open the [connectors](https://app.port.io/settings/data-sources) page in your Port environment and click on the integration you wish to add the mapping to. 2. Add the following mapping configuration to your exporter configuration: Adding the mapping configuration If you already have an exporter configuration, you can add the following mapping to your existing configuration by appending the lines after the `resources` key to your existing configuration. ``` resources: - kind: port.io/v1/ClusterEntities # Map entities of type 'Port Cluster Entities' selector: query: .spec.blueprint == "microservice" # This will make sure to only query ClusterEntites were .spec.blueprint == 'microservice' port: entity: mappings: - identifier: .spec.identifier title: .spec.identifier blueprint: .spec.blueprint properties: description: .spec.properties.description onCall: .spec.properties.onCall slackChannel: .spec.properties.slackChannel datadogUrl: .spec.properties.datadogUrl language: .spec.properties.language ``` 3. Click on the `Save & Resync` button to apply the configuration to your integration. You will now be able to see the newly exported entity in your Port environment. --- # Templates To allow for a quick start when implementing Port's k8s exporter, custom pre-built templates were created. These configurations will map common use-cases for when working with kubernetes. Templates need customization * Files used in the pre-built configurations can be found [here](https://github.com/port-labs/template-assets/tree/main/kubernetes) * These use-cases are generic, and might not always fit your specific use-case. Achieving custom use-cases might require some extra configuration of the k8s exporter ## Kubernetes quickstarts[​](#kubernetes-quickstarts "Direct link to Kubernetes quickstarts") ## [📄️Istio](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/istio/.md) [Istio quickstart](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/istio/.md) ## [📄️Knative](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/knative/.md) [Knative quickstart](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/knative/.md) ## [📄️Red Hat Openshift](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/openshift/.md) [Openshift quickstart](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/openshift/.md) ## [📄️Trivy Operator](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/trivy/.md) [Trivy quickstart](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/trivy/.md) ## [📄️Kyverno](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/kyverno/.md) [Kyverno quickstart](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/kyverno/.md) ## [📄️FluxCD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/fluxcd/.md) [FluxCD quickstart](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/fluxcd/.md) ## [📄️Kubernetes (advanced)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/full-kubernetes-exporter/.md) [Advanced Kubernetes installation](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/full-kubernetes-exporter/.md) --- # FluxCD [FluxCD](https://fluxcd.io/) is a set of continuous and progressive delivery solutions for Kubernetes that are open and extensible. Using Port's Kubernetes Exporter, you can keep track of all Flux resources in your cluster and export monitored repositories and applications to Port. You will use built in metadata from your kubernetes resources and CRDs to create entities in Port and keep track of their state. Our Kubernetes exporter basics Get to know the basics of our Kubernetes exporter [here!](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ![Flux blueprint graph with Namespace and Cluster](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8sFluxView.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * This guide assumes you have completed the [visualize your services' k8s runtime guide](https://docs.port.io/guides/all/visualize-service-k8s-runtime/) * [Helm](https://helm.sh) must be installed to use the chart. Please refer to Helm's [documentation](https://helm.sh/docs) to get started * The `jq` command must installed * The `yq` command must installed * The `kubectl` command must be installed * Have your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) ready. In this use-case, you will use a custom bash script which will assist you in the process of installing Port's K8s exporter. The script will install the helm chart to the Kubernetes cluster which is currently in kubectl context. To view the context name of the cluster the exporter will be installed on, run: ``` kubectl config current-context ``` ## Setting up blueprints & resource mapping[​](#setting-up-blueprints--resource-mapping "Direct link to Setting up blueprints & resource mapping") The following section will guide you through the process of setting up your blueprints and resource mapping using the installation script. You can read more about the installation script [here](#how-does-the-installation-script-work). ### Creating blueprints[​](#creating-blueprints "Direct link to Creating blueprints") The installation script provides a convenient way to create your blueprints. Using the `CUSTOM_BP_PATH` environment variable, you can fetch a pre-defined `blueprints.json` to create your blueprints. For this use-case, you will use [this file](https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/fluxcd-blueprints.json) to define your blueprints. Do this by running: ``` export CUSTOM_BP_PATH="https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/fluxcd-blueprints.json" ``` This `blueprints.json` file defines the following blueprints: * Cluster * Namespace * Workload * Flux Source * Flux Application Blueprint information * `Workload` is an abstraction of Kubernetes objects which create and manage pods. By creating this blueprint, you can avoid creating a dedicated blueprint per Workload type, all of which will likely look pretty similar. Here is the list of kubernetes objects `Workload` will represent: * Deployment * StatefulSet * DaemonSet * `Flux Source` is one of the most important Flux resource that defines the origin of a repository containing the desired state of the system and the requirements to obtain it. This blueprint tracks **GitRepository** and **HelmRepository** CRDs in the Flux system. * `Flux Application` is another important Flux resource that represents a local set of Kubernetes resources that Flux is supposed to reconcile in the cluster. This blueprint tracks **Kustomization** and **HelmRelease** CRDs in the Flux system. Below are the Flux blueprint schemas used in the exporter: **Flux source blueprint (click to expand)** Create in Port ``` { "identifier":"fluxSource", "description":"Flux Source", "title":"Flux Source", "icon":"Fluxcd", "schema":{ "properties":{ "repoURL":{ "type":"string", "icon":"Git", "title":"Repository URL", "description":"The URL of the repository containing the application source code" }, "sourceType":{ "icon":"DefaultProperty", "title":"Source Type", "description":"The flux source type", "type":"string", "enum":[ "HelmRepository", "GitRepository" ], "enumColors":{ "HelmRepository":"turquoise", "GitRepository":"green" } }, "interval":{ "icon":"Clock", "type":"string", "title":"Interval", "description":"Interval at which the GitRepository URL is checked for updates" }, "createdAt":{ "title":"Created At", "type":"string", "format":"date-time", "icon":"DefaultProperty" }, "branch":{ "title":"Branch", "type":"string", "icon":"DefaultProperty" } }, "required":[] }, "mirrorProperties":{}, "calculationProperties":{}, "aggregationProperties":{}, "relations":{ "namespace":{ "title":"Namespace", "target":"namespace", "required":false, "many":false } } } ``` **Flux application blueprint (click to expand)** Create in Port ``` { "identifier":"fluxApplication", "description":"This blueprint represents Flux Application which can be HelmRelease or Kustomization", "title":"Flux Application", "icon":"Fluxcd", "schema":{ "properties":{ "targetNamespace":{ "icon":"DefaultProperty", "type":"string", "title":"Target Namespace" }, "namespace":{ "type":"string", "title":"Namespace", "icon":"DefaultProperty" }, "ready":{ "icon":"DefaultProperty", "title":"Health Status", "description":"The health status of the application", "type":"string", "enum":[ "True", "False", "Unknown" ], "enumColors":{ "True":"green", "False":"red", "Unknown":"yellow" } }, "createdAt":{ "title":"Created At", "type":"string", "format":"date-time", "icon":"DefaultProperty" }, "applicationType":{ "icon":"DefaultProperty", "title":"Application Type", "description":"Kustomization or HelmRelease", "type":"string", "enum":[ "HelmRelease", "Kustomization" ], "enumColors":{ "HelmRelease":"lightGray", "Kustomization":"lightGray" } }, "interval":{ "icon":"Clock", "type":"string", "title":"Interval", "description":"The interval at which the application will be reconciled" }, "path":{ "title":"Path", "type":"string", "icon":"DefaultProperty" }, "prune":{ "title":"Prune", "type":"boolean", "icon":"DefaultProperty" } }, "required":[] }, "mirrorProperties":{}, "calculationProperties":{}, "aggregationProperties":{}, "relations":{ "source":{ "title":"Source", "target":"fluxSource", "required":false, "many":false } } } ``` ### Exporting custom resource mapping[​](#exporting-custom-resource-mapping "Direct link to Exporting custom resource mapping") Using the `CONFIG_YAML_URL` parameter, you can define a custom resource mapping to use when installing the exporter. In this use-case you will be using the **[this configuration file](https://github.com/port-labs/template-assets/blob/main/kubernetes/templates/fluxcd-kubernetes_v1_config.yaml)**. To achieve this, run: ``` export CONFIG_YAML_URL="https://github.com/port-labs/template-assets/blob/main/kubernetes/templates/fluxcd-kubernetes_v1_config.yaml" ``` Below is the mapping for the Flux resources: **Flux source mapping (click to expand)** ``` - kind: source.toolkit.fluxcd.io/v1/gitrepositories selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name icon: '"Fluxcd"' blueprint: '"fluxSource"' properties: repoURL: .spec.url sourceType: .kind branch: .spec.ref.branch interval: .spec.interval createdAt: .metadata.creationTimestamp relations: namespace: .metadata.namespace + "-" + env.CLUSTER_NAME - kind: source.toolkit.fluxcd.io/v1beta2/helmrepositories selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name icon: '"Fluxcd"' blueprint: '"fluxSource"' properties: repoURL: .spec.url sourceType: .kind branch: .spec.ref.branch interval: .spec.interval createdAt: .metadata.creationTimestamp relations: namespace: .metadata.namespace + "-" + env.CLUSTER_NAME ``` **Flux application mapping (click to expand)** ``` - kind: kustomize.toolkit.fluxcd.io/v1/kustomizations selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name icon: '"Fluxcd"' blueprint: '"fluxApplication"' properties: targetNamespace: .spec.targetNamespace namespace: .metadata.namespace ready: .status.conditions[] | select(.type == "Ready") | .status path: .spec.path prune: .spec.prune applicationType: .kind interval: .spec.interval createdAt: .metadata.creationTimestamp relations: source: .spec.sourceRef.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME - kind: helm.toolkit.fluxcd.io/v2beta2/helmreleases selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name icon: '"Fluxcd"' blueprint: '"fluxApplication"' properties: targetNamespace: .spec.targetNamespace namespace: .metadata.namespace ready: .status.conditions[] | select(.type == "Ready") | .status path: .spec.path prune: .spec.prune applicationType: .kind interval: .spec.chart.spec.interval createdAt: .metadata.creationTimestamp relations: source: .spec.chart.spec.sourceRef.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME ``` You can now browse to your Port environment to see that your blueprints have been created, and your k8s and Flux resources are being reported to Port using the freshly installed k8s exporter. --- # Kubernetes (advanced) Read the K8s basics first This use-case is an extension of the [basic Kubernetes use-case](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md). If you haven't already, we recommend you to read it first. Kubernetes has become one of the most popular ways to deploy microservice based applications. As the number of your microservices grow, and more clusters are deployed across several regions, it becomes complicated and tedious to keep track of all of your deployments, services, and jobs. Using Port's Kubernetes Exporter, you can keep track of your K8s resources and export all the data to Port. You will use K8s' built in metadata to create Entities in Port and keep track of their state. Learn the Kubernetes exporter basics Get to know the basics of our Kubernetes exporter [here!](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ![Full Kubernetes blueprint graph relations](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8sAdvancedView.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * This guide assumes you have completed the [visualize your services' k8s runtime guide](https://docs.port.io/guides/all/visualize-service-k8s-runtime/) * [Helm](https://helm.sh) must be installed to use the chart. Please refer to Helm's [documentation](https://helm.sh/docs) to get started * The `jq` command must installed * The `yq` command must installed * The `kubectl` command must be installed * Have your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) ready. In this use-case, you will use a custom bash script which will assist you in the process of installing Port's K8s exporter. The script will install the helm chart to the Kubernetes cluster which is currently in kubectl context. To view the context name of the cluster the exporter will be installed on, run: ``` kubectl config current-context ``` ## Setting up blueprints & resource mapping[​](#setting-up-blueprints--resource-mapping "Direct link to Setting up blueprints & resource mapping") The following section will guide you through the process of setting up your blueprints and resource mapping using the installation script. You can read more about the installation script [here](#how-does-the-installation-script-work). ### Creating blueprints[​](#creating-blueprints "Direct link to Creating blueprints") The installation script provides a convenient way to create your blueprints. Using the `CUSTOM_BP_PATH` environment variable, you can fetch a pre-defined `blueprints.json` to create your blueprints. For this use-case, you will use [this file](https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/kubernetes_complete_usecase_bps.json) to define your blueprints. Do this by running: ``` export CUSTOM_BP_PATH="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/blueprints/kubernetes_complete_usecase_bps.json" ``` This `blueprints.json` file defines the following blueprints: * Cluster. * Namespace. * Node. * Pod. * ReplicaSet * Workload \*. Workload covers multiple K8s types * `Workload` is an abstraction of Kubernetes objects which create and manage pods. By creating this blueprint, you can avoid creating a dedicated blueprint per Workload type, all of which will likely look pretty similar. Here is the list of kubernetes objects `Workload` will represent: * Deployment. * StatefulSet. * DaemonSet. ### Exporting custom resource mapping[​](#exporting-custom-resource-mapping "Direct link to Exporting custom resource mapping") Using the `CONFIG_YAML_URL` parameter, you can define a custom resource mapping to use when installing the exporter. In this use-case you will be using **[this configuration file](https://github.com/port-labs/template-assets/blob/main/kubernetes/kubernetes_v1_config.yaml)**. To achieve this, run: ``` export CONFIG_YAML_URL="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/kubernetes_v1_config.yaml" ``` You can now run the installation script using the following code snippet: ``` export CLUSTER_NAME="my-cluster" export PORT_CLIENT_ID="my-port-client-id" export PORT_CLIENT_SECRET="my-port-client-secret" curl -s https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/install.sh | bash ``` That's it! You can now browse to your Port environment to see that your blueprints have been created, and entities are being reported to Port using the freshly installed k8s exporter. ## Summary[​](#summary "Direct link to Summary") In this use-case, using the installation script, you: * set up your Port environment by creating blueprints defining different k8s resources. * installed Port's k8s exporter with a configuration allowing you to export important data from your cluster. * fetched k8s resources from you cluster as entities to your Port environment ## How does the installation script work?[​](#how-does-the-installation-script-work "Direct link to How does the installation script work?") Port's K8s exporter installation script will assist you in the process of installing Port's K8s exporter template using helm chart. This script will help you with: * setting up your custom Port [blueprints](/context-lake/data-model/setup-blueprint/overview/.md). * installing Port's k8s exporter using helm (check out the Helm chart's [source code](https://github.com/port-labs/helm-charts/tree/main/charts/port-k8s-exporter)); * deploying your custom [integration resources configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md#exporter-configuration-structure) View the installation bash script You can view the bash script [here](https://github.com/port-labs/template-assets/blob/main/kubernetes/install.sh). ### General installation configuration[​](#general-installation-configuration "Direct link to General installation configuration") The script supports configuration via environment variables. For each variable you'd like to set, run the following command before running the script: ``` export {VARIABLE_NAME}={value} ``` | Environment Variable | Description | Default | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | `PORT_CLIENT_ID` | **Required** - Your Port organization's Client ID used to authenticate the exporter to Port | | | `PORT_CLIENT_SECRET` | **Required** - Your Port organization's Client Secret used to authenticate the exporter to Port | | | `CUSTOM_BP_PATH` | **Required** - The URL/path to a json file with an array of blueprint objects to create. Can be either a `https://domain.com/path/to/blueprint.json` format URL, or a local path to a file `envs/production/blueprint.json`. | | | `CONFIG_YAML_URL` | **Required** - The URL/path to the desired integration resource mapping. Can be either an https format URL`https://domain.com/path/to/config.yaml`, or a local path to a file `envs/production/config.yaml` | `https://github.com/port-labs/template-assets/blob/main/kubernetes/kubernetes_config.yaml` | | `TARGET_NAMESPACE` | **Optional** - The Kubernetes namespace in which the exporter will be installed | `port-k8s-exporter` | | `DEPLOYMENT_NAME` | **Optional** - The Kubernetes deployment name the exporter will be installed as | `port-k8s-exporter` | | `CLUSTER_NAME` | **Optional** - The cluster's name as it will be exported to. Check out the note bellow for more information. | `my-cluster` | CLUSTER\_NAME and CUSTOM\_BP\_PATH behavior * **CLUSTER\_NAME:** The script will set the cluster's name as the `CLUSTER_NAME` environment variable. This name will be used as the cluster's name in integration resources configuration. If you'd like to change the cluster's name, you can do so by setting the `CLUSTER_NAME` environment variable before running the script. * **CUSTOM\_BP\_PATH:** It is important to order the blueprints while taking in to account the necessary relations for each blueprint. Once a blueprint was created, attempting to recreate it using the script will fail. To recreate a blueprint using the script, first delete the blueprint. --- # Istio [Istio](https://istio.io/latest/docs/setup/getting-started/) is an open-source service mesh that provides a uniform way to connect, manage, and secure microservices. Using Port's Kubernetes Exporter, you can keep track of all Istio resources across your different clusters and export all the data to Port. You will use built in metadata from your kubernetes resources and CRDs to create Entities in Port and keep track of their state. Learn the Kubernetes exporter basics Get to know the basics of our Kubernetes exporter [here!](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ![Istio blueprint graph with Namespace and Cluster](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8sIstioView.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * This guide assumes you have completed the [visualize your services' k8s runtime guide](https://docs.port.io/guides/all/visualize-service-k8s-runtime/) * [Helm](https://helm.sh) must be installed to use the chart. Please refer to Helm's [documentation](https://helm.sh/docs) to get started * The `jq` command must installed * The `yq` command must installed * The `kubectl` command must be installed * Have your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) ready. In this use-case, you will use a custom bash script which will assist you in the process of installing Port's K8s exporter. The script will install the helm chart to the Kubernetes cluster which is currently in kubectl context. To view the context name of the cluster the exporter will be installed on, run: ``` kubectl config current-context ``` ## Setting up blueprints & resource mapping[​](#setting-up-blueprints--resource-mapping "Direct link to Setting up blueprints & resource mapping") The following section will guide you through the process of setting up your blueprints and resource mapping using the installation script. You can read more about the installation script [here](#how-does-the-installation-script-work). ### Creating blueprints[​](#creating-blueprints "Direct link to Creating blueprints") The installation script provides a convenient way to create your blueprints. Using the `CUSTOM_BP_PATH` environment variable, you can fetch a pre-defined `blueprints.json` to create your blueprints. For this use-case, you will use [this file](https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/istio-blueprints.json) to define your blueprints. Do this by running: ``` export CUSTOM_BP_PATH="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/blueprints/istio-blueprints.json" ``` This `blueprints.json` file defines the following blueprints: * Cluster * Namespace * Workload * Istio Gateway * Istio Virtual Service Workload blueprint components * `Workload` is an abstraction of Kubernetes objects which create and manage pods. By creating this blueprint, you can avoid creating a dedicated blueprint per Workload type, all of which will likely look pretty similar. Here is the list of kubernetes objects `Workload` will represent: * Deployment * ReplicaSet * StatefulSet * DaemonSet Below are the Istio blueprint schemas used in the exporter: **Istio gateway blueprint (click to expand)** Create in Port ``` { "identifier": "gateways", "description": "This blueprint represents a service in our software catalog", "title": "Istio Gateways", "icon": "Cloud", "schema": { "properties": { "name": { "type": "string" }, "ports": { "type": "array" }, "labels": { "type": "object" }, "selector": { "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "namespace": { "target": "namespace", "required": true, "many": false } } } ``` **Istio virtual service blueprint (click to expand)** Create in Port ``` { "identifier": "virtualServices", "description": "This blueprint represents a service in our software catalog", "title": "Virtual Services", "icon": "Istio", "schema": { "properties": { "hosts": { "type": "array" }, "match": { "type": "array" }, "labels": { "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "gateways": { "target": "gateways", "many": true } } } ``` ### Exporting custom resource mapping[​](#exporting-custom-resource-mapping "Direct link to Exporting custom resource mapping") Using the `CONFIG_YAML_URL` parameter, you can define a custom resource mapping to use when installing the exporter. In this use-case you will be using the **[this configuration file](https://github.com/port-labs/template-assets/blob/main/kubernetes/templates/istio-kubernetes_v1_config.yaml)**. To achieve this, run: ``` export CONFIG_YAML_URL="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/templates/istio-kubernetes_v1_config.yaml" ``` Below is the mapping for the Istio resources: **Istio gateway mapping (click to expand)** ``` - kind: networking.istio.io/v1beta1/gateways selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace blueprint: '"gateways"' properties: title: .metadata.name ports: .spec.servers[].port.number name: .metadata.name labels: .metadata.labels selector: .spec.selector relations: namespace: .metadata.namespace ``` **Istio virtual service mapping (click to expand)** ``` - kind: networking.istio.io/v1beta1/virtualservices selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace blueprint: '"virtualServices"' properties: title: .metadata.name hosts: .spec.hosts match: .spec.http[].match labels: .metadata.labels relations: gateways: .spec.gateways[] + "-" + .metadata.namespace services: .metadata.namespace as $namespace | .spec.http[].route[].destination.host + "-" + $namespace ``` You can now browse to your Port environment to see that your blueprints have been created, and your k8s and Istio resources are being reported to Port using the freshly installed k8s exporter. --- # Knative [Knative](https://knative.dev/docs/) is an open-source community project, which enhances Kubernetes by introducing components that facilitate the deployment, execution, and administration of serverless, cloud-native applications. Using Port's Kubernetes Exporter, you can keep track of the Knative resources across your different clusters and export all of the data to Port. You will use built in metadata from your kubernetes resources and CRDs to create Entities in Port and keep track of their state. Learn the Kubernetes exporter basics Get to know the basics of our Kubernetes exporter [here!](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ![Knative blueprint graph with Namespace and Cluster](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8sKnativeView.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * This guide assumes you have completed the [visualize your services' k8s runtime guide](https://docs.port.io/guides/all/visualize-service-k8s-runtime/) * [Helm](https://helm.sh) must be installed to use the chart. Please refer to Helm's [documentation](https://helm.sh/docs) to get started * The `jq` command must installed * The `yq` command must installed * The `kubectl` command must be installed * Have your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) ready. In this use-case, you will use a custom bash script which will assist you in the process of installing Port's K8s exporter. The script will install the helm chart to the Kubernetes cluster which is currently in kubectl context. To view the context name of the cluster the exporter will be installed on, run: ``` kubectl config current-context ``` ## Setting up blueprints & resource mapping[​](#setting-up-blueprints--resource-mapping "Direct link to Setting up blueprints & resource mapping") The following section will guide you through the process of setting up your blueprints and resource mapping using the installation script. You can read more about the installation script [here](#how-does-the-installation-script-work). ### Creating blueprints[​](#creating-blueprints "Direct link to Creating blueprints") The installation script provides a convenient way to create your blueprints. Using the `CUSTOM_BP_PATH` environment variable, you can fetch a pre-defined `blueprints.json` to create your blueprints. For this use-case, you will use [this file](https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/kubernetes_knative_usecase.json) to define your blueprints. Do this by running: ``` export CUSTOM_BP_PATH="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/blueprints/kubernetes_knative_usecase.json" ``` This `blueprints.json` file defines the following blueprints: * Cluster * Namespace * Workload * Knative Service * Knative Configuration * Knative Revision * Knative Route Workload covers multiple K8s types * `Workload` is an abstraction of Kubernetes objects which create and manage pods. By creating this blueprint, you can avoid creating a dedicated blueprint per Workload type, all of which will likely look pretty similar. Here is the list of kubernetes objects `Workload` will represent: * Deployment * ReplicaSet * StatefulSet * DaemonSet Below are the Knative blueprint schemas used in the exporter: **Knative service blueprint (click to expand)** Create in Port ``` { "identifier": "knativeService", "description": "This blueprint represents a Knative service", "title": "Knative Service", "icon": "Service", "schema": { "properties": { "creationTimestamp": { "icon": "Clock", "type": "string", "title": "Created", "description": "When the Knative service was created", "format": "date-time" }, "labels": { "icon": "BlankPage", "type": "object", "title": "Labels", "description": "Labels of the Knative service" }, "containerConcurrency": { "type": "number", "title": "Container Concurrency" }, "containers": { "icon": "Docker", "title": "Containers", "type": "array", "description": "The array of containers configured for this service" }, "trafficConfiguration": { "icon": "Cloud", "title": "Traffic Configuration", "type": "array", "description": "The traffic configuration of this service" }, "url": { "title": "URL", "type": "string", "description": "The knative service's URL", "icon": "RestApi", "format": "url" }, "generation": { "title": "Generation", "description": "The Knative service's generation", "type": "number" }, "ready": { "title": "Ready", "type": "string", "enum": [ "True", "False" ], "enumColors": { "True": "green", "False": "red" } }, "serviceTimeout": { "icon": "DefaultProperty", "title": "Service Timeout(s)", "description": "The timeout configured for this service in seconds", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "namespace": { "title": "Namespace", "target": "namespace", "required": false, "many": false } } } ``` **Knative configuration blueprint (click to expand)** Create in Port ``` { "identifier": "knativeConfiguration", "description": "This blueprint represents a Knative Configuration", "title": "Knative Configuration", "icon": "CICD", "schema": { "properties": { "creationTimestamp": { "icon": "Clock", "type": "string", "title": "Created", "description": "When the Knative service was created", "format": "date-time" }, "labels": { "icon": "BlankPage", "type": "object", "title": "Labels", "description": "Labels of the Knative service" }, "ready": { "title": "Ready", "type": "string", "enum": [ "True", "False" ], "enumColors": { "True": "green", "False": "red" } }, "containers": { "icon": "Docker", "title": "Containers", "type": "array", "description": "Container specs for this configuration" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "knativeService": { "title": "Kative Service", "description": "The service managing this configuration", "target": "knativeService", "required": false, "many": false } } } ``` **Knative revision blueprint (click to expand)** Create in Port ``` { "identifier": "knativeRevision", "description": "This blueprint represents a Knative revision", "title": "Knative Revision", "icon": "Docker", "schema": { "properties": { "creationTimestamp": { "icon": "Clock", "type": "string", "title": "Created", "description": "When the Knative service was created", "format": "date-time" }, "labels": { "icon": "BlankPage", "type": "object", "title": "Labels", "description": "Labels of the Knative service" }, "isActive": { "type": "string", "title": "Is Active", "description": "Is the revision active currently", "enum": [ "True", "False" ], "enumColors": { "True": "green", "False": "red" } }, "containers": { "icon": "Docker", "title": "Containers", "type": "array", "description": "This revisions container configuration" }, "replicaCount": { "title": "Replica Count", "description": "This revision's current replica count", "type": "number" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "knativeConfiguration": { "title": "Knative Configuration", "description": "The revisions configuration", "target": "knativeConfiguration", "required": false, "many": false } } } ``` **Knative route blueprint (click to expand)** Create in Port ``` { "identifier": "knativeRoute", "description": "This blueprint represents a Knative Route in our k8s cluster", "title": "Knative Route", "icon": "Cloud", "schema": { "properties": { "creationTimestamp": { "icon": "Clock", "type": "string", "title": "Created", "description": "When the Knative service was created", "format": "date-time" }, "labels": { "icon": "DefaultProperty", "type": "object", "title": "Labels", "description": "Labels of the Knative service" }, "trafficConfiguration": { "icon": "DefaultProperty", "title": "Traffic Configuration", "type": "array", "description": "The routes traffic configuration" }, "url": { "icon": "RestApi", "title": "URL", "type": "string", "description": "The URL of this route", "format": "url" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "knativeRevision": { "title": "knativeRevision", "target": "knativeRevision", "required": false, "many": true }, "knativeService": { "title": "Knative Service", "target": "knativeService", "required": false, "many": false } } } ``` ### Exporting custom resource mapping[​](#exporting-custom-resource-mapping "Direct link to Exporting custom resource mapping") Using the `CONFIG_YAML_URL` parameter, you can define a custom resource mapping to use when installing the exporter. In this use-case you will be using **[this configuration file](https://github.com/port-labs/template-assets/blob/main/kubernetes/full-configs/kubernetes_kantive_usecase.yaml)**. To achieve this, run: ``` export CONFIG_YAML_URL="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/full-configs/kubernetes_kantive_usecase.yaml" ``` Below is the mapping for the Knative resources: **Knative service mapping (click to expand)** ``` - kind: serving.knative.dev/v1/service selector: query: .metadata.name | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"knativeService"' properties: creationTimestamp: .metadata.creationTimestamp labels: .metadata.labels containerConcurrency: .spec.template.spec.containerConcurrency containers: .spec.template.spec.containers trafficConfiguration: .status.traffic serviceTimeout: .spec.template.spec.timeoutSeconds url: .status.url ready: .status.conditions[] | select(.type == "Ready") | .status generation: .metadata.generation relations: namespace: .metadata.namespace + "-" + env.CLUSTER_NAME ``` **Knative configuration mapping (click to expand)** ``` - kind: serving.knative.dev/v1/configuration selector: query: .metadata.name | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"knativeConfiguration"' properties: creationTimestamp: .metadata.creationTimestamp labels: .metadata.labels ready: .status.conditions[] | select(.type == "Ready") | .status containers: .spec.template.spec.containers relations: knativeService: .metadata.ownerReferences[0].name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME ``` **Knative revision mapping (click to expand)** ``` - kind: serving.knative.dev/v1/revision selector: query: .metadata.name | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"knativeRevision"' properties: creationTimestamp: .metadata.creationTimestamp labels: .metadata.labels isActive: .status.conditions[] | select(.type == "Active") | .status containers: .spec.containers replicaCount: .status.actualReplicas relations: knativeConfiguration: .metadata.ownerReferences[0].name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME ``` **Knative route mapping (click to expand)** ``` - kind: serving.knative.dev/v1/route selector: query: .metadata.name | startswith("kube") | not port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name blueprint: '"knativeRoute"' properties: creationTimestamp: .metadata.creationTimestamp labels: .metadata.labels trafficConfiguration: .status.traffic url: .status.url relations: knativeRevision: '[.status.traffic[].revisionName + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME]' knativeService: .metadata.ownerReferences[0].name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME ``` You can now browse to your Port environment to see that your blueprints have been created, and your k8s and Knative resources are being reported to Port using the freshly installed k8s exporter. --- # Kyverno [Kyverno](https://kyverno.io/) is a policy engine designed for Kubernetes. Kyverno policies can validate, mutate, generate, and cleanup Kubernetes resources, allowing cluster administrators to enforce configuration best practices for their clusters. Using Port's Kubernetes Exporter, you can keep track of all Kyverno resources across your different clusters and export all the policies and reports to Port. You will use built in metadata from your kubernetes resources and CRDs to create entities in Port and keep track of their state. Our Kubernetes exporter basics Get to know the basics of our Kubernetes exporter [here!](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ![Kyverno blueprint graph with Namespace and Cluster](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8sKyvernoView.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * This guide assumes you have completed the [visualize your services' k8s runtime guide](https://docs.port.io/guides/all/visualize-service-k8s-runtime/) * [Helm](https://helm.sh) must be installed to use the chart. Please refer to Helm's [documentation](https://helm.sh/docs) to get started * The `jq` command must installed * The `yq` command must installed * The `kubectl` command must be installed * Have your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) ready. In this use-case, you will use a custom bash script which will assist you in the process of installing Port's K8s exporter. The script will install the helm chart to the Kubernetes cluster which is currently in kubectl context. To view the context name of the cluster the exporter will be installed on, run: ``` kubectl config current-context ``` ## Setting up blueprints & resource mapping[​](#setting-up-blueprints--resource-mapping "Direct link to Setting up blueprints & resource mapping") The following section will guide you through the process of setting up your blueprints and resource mapping using the installation script. You can read more about the installation script [here](#how-does-the-installation-script-work). ### Creating blueprints[​](#creating-blueprints "Direct link to Creating blueprints") The installation script provides a convenient way to create your blueprints. Using the `CUSTOM_BP_PATH` environment variable, you can fetch a pre-defined `blueprints.json` to create your blueprints. For this use-case, you will use [this file](https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/kyverno-blueprints.json) to define your blueprints. Do this by running: ``` export CUSTOM_BP_PATH="https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/kyverno-blueprints.json" ``` This `blueprints.json` file defines the following blueprints: * Cluster * Namespace * Workload * Kyverno Policy * Kyverno Policy Report Blueprint information * `Workload` is an abstraction of Kubernetes objects which create and manage pods. By creating this blueprint, you can avoid creating a dedicated blueprint per Workload type, all of which will likely look pretty similar. Here is the list of kubernetes objects `Workload` will represent: * Deployment * StatefulSet * DaemonSet * `Kyverno Policy` is one of the most important Kyverno resources, giving developers the capability to set and enforce policy rules in their Kubernetes cluster. * `Kyverno Policy Report` is another important Kyverno resource that contains the results of applying the policies to the Kubernetes cluster. Below are the Kyverno blueprint schemas used in the exporter: **Kyverno policy blueprint (click to expand)** Create in Port ``` { "identifier":"kyvernoPolicy", "title":"Kyverno Policy", "icon":"Cluster", "schema":{ "properties":{ "admission":{ "title":"Admission", "type":"boolean", "icon":"DefaultProperty" }, "background":{ "title":"Background", "type":"boolean", "icon":"DefaultProperty" }, "createdAt":{ "title":"Created At", "type":"string", "format":"date-time", "icon":"DefaultProperty" }, "validationFailureAction":{ "icon":"DefaultProperty", "title":"Validation Failure Action", "type":"string", "enum":[ "Audit", "Enforce" ], "enumColors":{ "Audit":"lightGray", "Enforce":"lightGray" } } }, "required":[] }, "mirrorProperties":{}, "calculationProperties":{}, "aggregationProperties":{}, "relations":{ "namespace":{ "title":"Namespace", "target":"namespace", "required":false, "many":false } } } ``` **Kyverno policy report blueprint (click to expand)** Create in Port ``` { "identifier":"kyvernoPolicyReport", "title":"Kyverno Policy Report", "icon":"Cluster", "schema":{ "properties":{ "createdAt":{ "title":"Created At", "type":"string", "format":"date-time" }, "pass":{ "title":"Pass", "type":"number" }, "fail":{ "title":"Fail", "type":"number" }, "warn":{ "title":"Warn", "type":"number" }, "error":{ "title":"Error", "type":"number" }, "skip":{ "title":"Skip", "type":"number" } }, "required":[] }, "mirrorProperties":{}, "calculationProperties":{}, "aggregationProperties":{}, "relations":{ "namespace":{ "title":"Namespace", "target":"namespace", "required":false, "many":false } } } ``` ### Exporting custom resource mapping[​](#exporting-custom-resource-mapping "Direct link to Exporting custom resource mapping") Using the `CONFIG_YAML_URL` parameter, you can define a custom resource mapping to use when installing the exporter. In this use-case you will be using the **[this configuration file](https://github.com/port-labs/template-assets/blob/main/kubernetes/templates/kyverno-kubernetes_v1_config.yaml)**. To achieve this, run: ``` export CONFIG_YAML_URL="https://github.com/port-labs/template-assets/blob/main/kubernetes/templates/kyverno-kubernetes_v1_config.yaml" ``` Below is the mapping for the Kyverno resources: **Kyverno policy mapping (click to expand)** ``` - kind: kyverno.io/v1/policies selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name icon: '"Cluster"' blueprint: '"kyvernoPolicy"' properties: admission: .spec.admission background: .spec.background validationFailureAction: .spec.validationFailureAction createdAt: .metadata.creationTimestamp relations: namespace: .metadata.namespace + "-" + env.CLUSTER_NAME - kind: kyverno.io/v1/clusterpolicies selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + env.CLUSTER_NAME title: .metadata.name icon: '"Cluster"' blueprint: '"kyvernoPolicy"' properties: admission: .spec.admission background: .spec.background validationFailureAction: .spec.validationFailureAction createdAt: .metadata.creationTimestamp ``` **Kyverno policy report mapping (click to expand)** ``` - kind: wgpolicyk8s.io/v1alpha2/policyreports selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .scope.name icon: '"Cluster"' blueprint: '"kyvernoPolicyReport"' properties: pass: .summary.pass fail: .summary.fail warn: .summary.warn error: .summary.error skip: .summary.skip createdAt: .metadata.creationTimestamp relations: namespace: .metadata.namespace + "-" + env.CLUSTER_NAME - kind: wgpolicyk8s.io/v1alpha2/clusterpolicyreports selector: query: 'true' port: entity: mappings: - identifier: .metadata.name + "-" + env.CLUSTER_NAME title: .scope.name icon: '"Cluster"' blueprint: '"kyvernoPolicyReport"' properties: pass: .summary.pass fail: .summary.fail warn: .summary.warn error: .summary.error skip: .summary.skip createdAt: .metadata.creationTimestamp ``` You can now browse to your Port environment to see that your blueprints have been created, and your k8s and Kyverno resources are being reported to Port using the freshly installed k8s exporter. --- # Red Hat Openshift [Red Hat Openshift](https://www.redhat.com/en/technologies/cloud-computing/openshift) is a versatile platform for scalable application development, modernization, and deployment over Kubernetes, offering a complete service set for app delivery on your preferred infrastructure. Using Port's Kubernetes Exporter, you can keep track of important Openshift resources across your different clusters and export the data to Port. You will use built in metadata from your Openshift resources and CRDs to create entities in Port and keep track of their state. Learn the Kubernetes exporter basics Get to know the basics of our Kubernetes exporter [here!](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ## Mapping Red Hat Openshift - Goals[​](#mapping-red-hat-openshift---goals "Direct link to Mapping Red Hat Openshift - Goals") While Red Hat Openshift provides great visibility when it comes to your Openshift (Kubernetes) environments, there are still some questions that remain about how your Openshift environment connects and interacts with the rest of your infrastructure, for example: * Which cloud provider is the cluster running in? * Which VPC is the cluster running in? * Who is on-call for a given cluster? * What are all the endpoints provided by all different Openshift clusters in a cloud region? Importing your Openshift resources to Port makes it easy to create multiple tailored views for different use cases. For example, you can create a view that shows you how your Openshift cluster interacts with the rest of your infrastructure, or you can create a high-level view that allows management to understand the business value provided by your Openshift installations. In this example you will map your Openshift clusters, their workloads and the Openshift routes which are exposed by your different clusters. Learn the Kubernetes exporter basics Get to know the basics of our Kubernetes exporter [here!](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ![OpenShift blueprint graph with Namespace and Cluster](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8sOpenshiftView.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * This guide assumes you have completed the [visualize your services' k8s runtime guide](https://docs.port.io/guides/all/visualize-service-k8s-runtime/) * [Helm](https://helm.sh) must be installed to use the chart. Please refer to Helm's [documentation](https://helm.sh/docs) to get started * The `jq` command must installed * The `yq` command must installed * The `kubectl` command must be installed * Have your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) ready. In this use-case, you will use a custom bash script which will assist you in the process of installing Port's K8s exporter. The script will install the helm chart to the Kubernetes cluster which is currently in kubectl context. To view the context name of the cluster the exporter will be installed on, run: ``` kubectl config current-context ``` ## Setting up blueprints & resource mapping[​](#setting-up-blueprints--resource-mapping "Direct link to Setting up blueprints & resource mapping") The following section will guide you through the process of setting up your blueprints and resource mapping using the installation script. You can read more about the installation script [here](#how-does-the-installation-script-work). ### Creating blueprints[​](#creating-blueprints "Direct link to Creating blueprints") The installation script provides a convenient way to create your blueprints. Using the `CUSTOM_BP_PATH` environment variable, you can fetch a pre-defined `blueprints.json` to create your blueprints. For this use-case, you will use [this file](https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/openshift-blueprints.json) to define your blueprints. Do this by running: ``` export CUSTOM_BP_PATH="https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/openshift-blueprints.json" ``` This `blueprints.json` file defines the following blueprints: * Cluster * Namespace * Workload * Service * Openshift Route Workload and Route blueprints * `Workload` is an abstraction of Kubernetes objects which create and manage pods. By creating this blueprint, you can avoid creating a dedicated blueprint per Workload type, all of which will likely look pretty similar. Here is the list of kubernetes objects `Workload` will represent: * Deployment * StatefulSet * DaemonSet * `Openshift Route` is one of the most important Openshift resources, giving developers the capability to connect to their services, while the entire network layer is managed by the Openshift API, and providing a simple DNS record for accessibility. Below are the Openshift blueprint schemas used in the exporter: **Service blueprint (click to expand)** Create in Port ``` { "identifier": "k8sService", "title": "K8s Service", "description": "This blueprint represents a k8s Service", "icon": "Microservice", "schema": { "properties": { "creationTimestamp": { "type": "string", "title": "Created", "format": "date-time", "description": "When the Namespace was created", "icon": "DefaultProperty" }, "labels": { "type": "object", "title": "Labels", "description": "Labels of the Pod", "icon": "DefaultProperty" }, "type": { "type": "string", "title": "Service Type", "description": "Service Type", "icon": "DefaultProperty" }, "selectors": { "icon": "DefaultProperty", "type": "object", "title": "Service Selectors", "description": "Pod selectors for the service" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "pods": { "title": "Pods", "target": "pod", "required": false, "many": true }, "namespace": { "title": "Namespace", "target": "namespace", "required": false, "many": false } } } ``` **Openshift route blueprint (click to expand)** Create in Port ``` { "identifier": "openshiftRoute", "title": "Openshift Route", "description": "This blueprint represents an Openshift route", "icon": "EC2", "schema": { "properties": { "hostName": { "icon": "DefaultProperty", "title": "Hostname", "type": "string", "format": "url" }, "spec": { "title": "Spec", "type": "object" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "relations": { "service": { "title": "Service", "target": "k8sService", "required": false, "many": true } } } ``` ### Exporting custom resource mapping[​](#exporting-custom-resource-mapping "Direct link to Exporting custom resource mapping") Using the `CONFIG_YAML_URL` parameter, you can define a custom resource configuration to use when installing the exporter. In this use-case you will be using **[this configuration file](https://github.com/port-labs/template-assets/blob/main/kubernetes/full-configs/openshift_usecase.yaml)**. To achieve this, run: ``` export CONFIG_YAML_URL="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/full-configs/openshift_usecase.yaml" ``` Below is the mapping for the Openshift resources: **Service mapping (click to expand)** ``` - kind: v1/services selector: query: .metadata.namespace | startswith("kube") or startswith("openshift") | not port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name icon: '"Service"' blueprint: '"k8sService"' properties: creationTimestamp: .metadata.creationTimestamp labels: .metadata.labels type: .spec.type selectors: .spec.selector // null - kind: discovery.k8s.io/v1/endpointslices selector: query: (.metadata.namespace | startswith("kube") or startswith("openshift") | not) and (([.endpoints[].targetRef.name]) as $ref | if $ref != [null] then true else false end) port: entity: mappings: - identifier: .metadata.labels."kubernetes.io/service-name" + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME blueprint: '"k8sService"' relations: pods: .metadata.namespace as $namespace | ([.endpoints[].targetRef.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME]) ``` **Openshift route mapping (click to expand)** ``` - kind: route.openshift.io/v1/routes selector: query: .metadata.namespace | startswith("kube") or startswith("openshift") | not port: entity: mappings: - identifier: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .metadata.name + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME blueprint: '"openshiftRoute"' properties: hostname: .spec.host spec: .spec relations: service: .metadata.namespace as $namespace | [(.spec.alternateBackends[]?.name )] as $alt_backends | ($alt_backends + [.spec.to.name]) | map(. + "-" + $namespace + "-" + env.CLUSTER_NAME) ``` You can now browse to your Port environment to see that your blueprints have been created, and your Kubernetes resources, including Openshift routes are being reported to Port using the freshly installed k8s exporter. --- # Trivy Operator [Trivy Operator](https://github.com/aquasecurity/trivy-operator) is an open-source security scanner that leverages [Trivy](https://github.com/aquasecurity/trivy) to continuously scan your Kubernetes cluster for security issues. Using Port's Kubernetes Exporter, you can keep track of all Trivy resources across your different clusters and export all the security and configuration issues to Port. You will use built in metadata from your kubernetes resources and CRDs to create entities in Port and keep track of their state. Learn the Kubernetes exporter basics Get to know the basics of our Kubernetes exporter [here!](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ![Trivy blueprint graph with Workload and Cluster](/img/build-your-software-catalog/sync-data-to-catalog/kubernetes/k8sTrivyOperatorView.png) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * This guide assumes you have completed the [visualize your services' k8s runtime guide](https://docs.port.io/guides/all/visualize-service-k8s-runtime/) * [Helm](https://helm.sh) must be installed to use the chart. Please refer to Helm's [documentation](https://helm.sh/docs) to get started * The `jq` command must installed * The `yq` command must installed * The `kubectl` command must be installed * Have your [Port credentials](/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview/.md#find-your-port-credentials) ready. In this use-case, you will use a custom bash script which will assist you in the process of installing Port's K8s exporter. The script will install the helm chart to the Kubernetes cluster which is currently in kubectl context. To view the context name of the cluster the exporter will be installed on, run: ``` kubectl config current-context ``` ## Setting up blueprints & resource mapping[​](#setting-up-blueprints--resource-mapping "Direct link to Setting up blueprints & resource mapping") The following section will guide you through the process of setting up your blueprints and resource mapping using the installation script. You can read more about the installation script [here](#how-does-the-installation-script-work). ### Creating blueprints[​](#creating-blueprints "Direct link to Creating blueprints") The installation script provides a convenient way to create your blueprints. Using the `CUSTOM_BP_PATH` environment variable, you can fetch a pre-defined `blueprints.json` to create your blueprints. For this use-case, you will use [this file](https://github.com/port-labs/template-assets/blob/main/kubernetes/blueprints/trivy-blueprints.json) to define your blueprints. Do this by running: ``` export CUSTOM_BP_PATH="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/blueprints/trivy-blueprints.json" ``` This `blueprints.json` file defines the following blueprints: * Cluster * Namespace * Workload * Trivy Config Audit Report * Trivy Vulnerability Three blueprints required * `Workload` is an abstraction of Kubernetes objects which create and manage pods. By creating this blueprint, you can avoid creating a dedicated blueprint per Workload type, all of which will likely look pretty similar. Here is the list of kubernetes objects `Workload` will represent: * Deployment * StatefulSet * DaemonSet * `Trivy Config Audit Report` represents checks performed by Trivy against a Kubernetes object's configuration * `Trivy Vulnerability` represents the latest vulnerabilities found in a container image of a given Kubernetes workload Below are the Trivy blueprint schemas used in the exporter: **Trivy config audit report blueprint (click to expand)** Create in Port ``` { "identifier": "trivyConfigAuditReport", "title": "Trivy Config Audit Report", "icon": "Trivy", "schema": { "properties": { "category": { "title": "Category", "type": "string" }, "messages": { "title": "Messages", "type": "array" }, "description": { "title": "Description", "type": "string" }, "severity": { "title": "Severity", "type": "string", "enum": [ "LOW", "MEDIUM", "HIGH", "CRITICAL", "UNKNOWN" ], "enumColors": { "LOW": "green", "MEDIUM": "yellow", "HIGH": "red", "CRITICAL": "red", "UNKNOWN": "lightGray" } }, "remediation": { "title": "Remediation", "type": "string" }, "success": { "title": "Success", "type": "boolean" }, "scanner": { "title": "Scanner", "type": "string" }, "scannerVersion": { "title": "Scanner Version", "type": "string" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" }, "updatedAt": { "title": "Updated At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "kubernetes_resource": { "title": "Kubernetes Resource", "target": "workload", "required": false, "many": false } } } ``` **Trivy vulnerability blueprint (click to expand)** Create in Port ``` { "identifier": "trivyVulnerabilityReport", "title": "Trivy Vulnerability", "icon": "Trivy", "schema": { "properties": { "resource": { "title": "Resource", "type": "string" }, "score": { "title": "Score", "type": "number" }, "fixedVersion": { "title": "Fixed Version", "type": "string" }, "installedVersion": { "title": "Installed Version", "type": "string" }, "lastModifiedDate": { "title": "Last Modified Date", "type": "string", "format": "date-time" }, "links": { "icon": "DefaultProperty", "title": "Links", "type": "array", "items": { "type": "string", "format": "url" } }, "primaryLink": { "title": "Primary Link", "type": "string", "format": "url" }, "publishedDate": { "title": "Published Date", "type": "string", "format": "date-time" }, "severity": { "title": "Severity", "type": "string", "enum": [ "LOW", "MEDIUM", "HIGH", "CRITICAL", "UNKNOWN" ], "enumColors": { "LOW": "green", "MEDIUM": "yellow", "HIGH": "red", "CRITICAL": "red", "UNKNOWN": "lightGray" } }, "target": { "title": "Target", "type": "string" }, "scanner": { "title": "Scanner Name", "type": "string" }, "scannerVersion": { "title": "Scanner Version", "type": "string" }, "createdAt": { "title": "Created At", "type": "string", "format": "date-time" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "kubernetes_resource": { "title": "Kubernetes Resource", "target": "workload", "required": false, "many": false } } } ``` ### Exporting custom resource mapping[​](#exporting-custom-resource-mapping "Direct link to Exporting custom resource mapping") Using the `CONFIG_YAML_URL` parameter, you can define a custom resource mapping to use when installing the exporter. In this use-case you will be using the **[this configuration file](https://github.com/port-labs/template-assets/blob/main/kubernetes/templates/trivy-kubernetes_v1_config.yaml)**. To achieve this, run: ``` export CONFIG_YAML_URL="https://raw.githubusercontent.com/port-labs/template-assets/main/kubernetes/templates/trivy-kubernetes_v1_config.yaml" ``` Below are the mappings for the Trivy resources: **Trivy config audit report mapping (click to expand)** ``` - kind: aquasecurity.github.io/v1alpha1/configauditreports selector: query: 'true' port: itemsToParse: .report.checks entity: mappings: - identifier: .metadata.name + "-" + .item.checkID + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .item.title icon: '"Trivy"' blueprint: '"trivyConfigAuditReport"' properties: category: .item.category messages: .item.messages description: .item.description severity: .item.severity remediation: .item.remediation success: .item.success scanner: .report.scanner.name scannerVersion: .report.scanner.version createdAt: .metadata.creationTimestamp updatedAt: .report.updateTimestamp relations: kubernetes_resource: ( if (.metadata.ownerReferences | length > 0) then (.metadata.ownerReferences[] | select(.controller == true) | .name + "-" + .kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME ) else empty end ) ``` **Trivy vulnerability mapping (click to expand)** ``` - kind: aquasecurity.github.io/v1alpha1/vulnerabilityreports selector: query: 'true' port: itemsToParse: .report.vulnerabilities entity: mappings: - identifier: .metadata.name + "-" + .item.vulnerabilityID + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME title: .item.title icon: '"Trivy"' blueprint: '"trivyVulnerabilityReport"' properties: resource: .item.resource score: .item.score fixedVersion: .item.fixedVersion installedVersion: .item.installedVersion lastModifiedDate: .item.lastModifiedDate links: .item.links primaryLink: .item.primaryLink publishedDate: .item.publishedDate severity: .item.severity target: .item.target scanner: .report.scanner.name scannerVersion: .report.scanner.version createdAt: .metadata.creationTimestamp relations: kubernetes_resource: ( if (.metadata.ownerReferences | length > 0) then (.metadata.ownerReferences[] | select(.controller == true) | .name + "-" + .kind + "-" + .metadata.namespace + "-" + env.CLUSTER_NAME ) else empty end ) ``` ## Alternative integration using GitHub file ingestion[​](#alternative-integration-using-github-file-ingestion "Direct link to Alternative integration using GitHub file ingestion") While the Trivy Kubernetes exporter described above is the recommended installation method, you may prefer to use the GitHub file ingesting feature to [ingest your Trivy scan results to Port](/guides/all/ingest-repository-files-into-your-context-lake/.md?file-pattern=trivy). --- # Amplication ![Amplication icon](/img/guides/icons/Amplication.svg)![Amplication icon](/img/guides/icons/Amplication.svg) Loading version... Port's Amplication integration allows you to model Amplication resources in your software catalog, from which you can then automate code generation using predefined templates, ensuring standardization for resource creation. Supported resources ![External link icon](/img/icons/external-link.svg)[Template](https://docs.amplication.com/day-one/create-resource-from-template)[Resource](https://docs.amplication.com/day-one/overview)[Outdated Version Alert](https://docs.amplication.com/day-two/update-templates) ## Common use cases[​](#common-use-cases "Direct link to Common use cases") * Automate scaffolding of new services using Amplication's templates. * Ensure standardization by enforcing predefined golden paths for resource creation. * Leverage code generation to accelerate development and maintain consistency. * Poll Amplication for templates, resources, and outdated version alerts to keep you in sync with your Amplication resources. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### Amplication API token[​](#amplication-api-token "Direct link to Amplication API token") To generate a token: 1. Navigate to [Amplication](https://app.amplication.com/) and go to the settings tab of your workspace. 2. Navigate to the `API Tokens` section and generate a new token. 3. Copy the token and save it in a secure location. ### Amplication host URL[​](#amplication-host-url "Direct link to Amplication host URL") Your Amplication host URL is `https://server.amplication.com/graphql`. ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from Amplication's API into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true resources: - kind: amplication_template selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"AmplicationTemplate"' properties: description: .description project: .project.name project_id: .project.id blueprint: .blueprint.name blueprint_id: .blueprint.id - kind: amplication_resource selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"AmplicationResource"' properties: description: .description project: .project.name project_id: .project.id blueprint: .blueprint.name blueprint_id: .blueprint.id git_organization: .gitRepository.gitOrganization.provider git_repository: .gitRepository.gitOrganization.name + "/" + .gitRepository.name relations: template: if .serviceTemplate != null then .serviceTemplate.id else null end - kind: amplication_alert selector: query: 'true' port: entity: mappings: identifier: .id title: 'if .block != null then .type + ": " + .block.displayName else .type end' blueprint: '"AmplicationAlert"' properties: block_id: if .block != null then .block.id else null end block_displayName: if .block != null then .block.displayName else null end type: .type outdatedVersion: .outdatedVersion latestVersion: .latestVersion status: .status relations: resource: .resourceId ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. TemplateResourceOutdated Version Alert Default mappingYAML ``` - kind: amplication_template selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"AmplicationTemplate"' properties: description: .description project: .project.name project_id: .project.id blueprint: .blueprint.name blueprint_id: .blueprint.id ``` Sample payloadJSON ``` { "id": "cm6zln01a0209utjtlorazri1", "name": "Port Node.js Template", "description": "Template created from an existing resource", "resourceType": "ServiceTemplate", "project": { "id": "cm6zlfk2o01liutjtdw8xj7f0", "name": "Port Integration" }, "blueprint": { "id": "cm6gb3j00000p14gz2n11otq4", "name": "Node.js Service (Amplication's Standard)" } } ``` Port entityJSON ``` { "identifier": "cm6zln01a0209utjtlorazri1", "title": "Port Node.js Template", "team": [], "properties": { "name": "Port Node.js Template", "description": "Template created from an existing resource", "project": "Port Integration", "project_id": "cm6zlfk2o01liutjtdw8xj7f0", "blueprint": "Node.js Service (Amplication's Standard)", "blueprint_id": "cm6gb3j00000p14gz2n11otq4" }, "relations": {}, "icon": "Amplication" } ``` Default mappingYAML ``` - kind: amplication_resource selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"AmplicationResource"' properties: description: .description project: .project.name project_id: .project.id blueprint: .blueprint.name blueprint_id: .blueprint.id git_organization: .gitRepository.gitOrganization.provider git_repository: .gitRepository.gitOrganization.name + "/" + .gitRepository.name relations: template: if .serviceTemplate != null then .serviceTemplate.id else null end ``` Sample payloadJSON ``` { "id": "cm6gr9t4s0000jx5t8l8prvik", "name": "Sample Resource Name", "description": "General description for the resource", "resourceType": "Service", "project": { "id": "cm6gb3j0a000q14gzlq9m7h1o", "name": "Sample Project" }, "blueprint": { "id": "cm6gb3j00000p14gz2n11otq4", "name": "Node.js Service (Amplication's Standard)" }, "serviceTemplate": null, "gitRepository": { "name": "examplerepo", "gitOrganization": { "name": "examplecompany", "provider": "Github" } } } ``` Port entityJSON ``` { "identifier": "cm6gr9t4s0000jx5t8l8prvik", "title": "Sample Resource Name", "team": [], "properties": { "name": "Sample Resource Name", "description": "General description for the resource", "project": "Sample Project", "project_id": "cm6gb3j0a000q14gzlq9m7h1o", "git_organization": "Github", "git_repository": "examplecompany/examplerepo", "blueprint": "Node.js Service (Amplication's Standard)", "blueprint_id": "cm6gb3j00000p14gz2n11otq4" }, "relations": {}, "icon": "Amplication" } ``` Default mappingYAML ``` - kind: amplication_alert selector: query: 'true' port: entity: mappings: identifier: .id title: 'if .block != null then .type + ": " + .block.displayName else .type end' blueprint: '"AmplicationAlert"' properties: block_id: if .block != null then .block.id else null end block_displayName: if .block != null then .block.displayName else null end type: .type outdatedVersion: .outdatedVersion latestVersion: .latestVersion status: .status relations: resource: .resourceId ``` Sample payloadJSON ``` { "id": "cm71nzqfh00lftp1dh9bslk0n", "createdAt": "2025-02-12T08:44:11.022Z", "updatedAt": "2025-02-12T08:44:11.022Z", "resourceId": "cm71nzdyn00kltp1dyoe4abpu", "blockId": "cm67mln9k004k55uul3j4ywww", "block": { "id": "cm67mln9k004k55uul3j4ywww", "displayName": "Resource Template Version" }, "type": "TemplateVersion", "outdatedVersion": "0.1.0", "latestVersion": "0.2.0", "status": "New" } ``` Port entityJSON ``` { "identifier": "cm71nzqfh00lftp1dh9bslk0n", "title": "TemplateVersion: Resource Template Version", "icon": "Amplication", "team": [], "properties": { "block_id": "cm67mln9k004k55uul3j4ywww", "block_displayName": "Resource Template Version", "type": "PluginVersion", "outdatedVersion": "0.1.0", "latestVersion": "0.2.0", "status": "New" }, "relations": { "resource": "cm6gr9t4s0000jx5t8l8prvik" } } ``` --- # Backstage ![Backstage icon](/img/guides/icons/Backstage.svg)![Backstage icon](/img/guides/icons/dark/Backstage.svg) Loading version... Port's Backstage integration allows you to ingest Backstage entities into your context lake so that agents, workflows, and your team members can access and act on them. Supported resources ![External link icon](/img/icons/external-link.svg)[Entities API](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-entitiesby-query)[Component](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component)[Template](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template)[API](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api)[Group](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group)[User](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user)[Resource](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-resource)[System](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system)[Domain](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-domain) ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") ### Create a Backstage token[​](#create-a-backstage-token "Direct link to Create a Backstage token") Port will authenticate to Backstage via [static tokens](https://backstage.io/docs/auth/service-to-service-auth/#static-tokens). Configure a token for Port using the following Backstage configuration: ``` backend: auth: externalAccess: - type: static options: token: YOUR-TOKEN subject: port-ocean-access ``` Replace `YOUR-TOKEN` with your Backstage token. To create a token, Backstage recommends to use the following command: ``` node -p 'require("crypto").randomBytes(24).toString("base64")' ``` ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` resources: - kind: component selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"component"' properties: type: .spec.type lifecycle: .spec.lifecycle language: .spec.language description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef system: '"system:" + (.metadata.namespace // "default") + "/" + .spec.system' subcomponentOf: .relations[] | select(.type == "subcomponentOf" and (.targetRef | startswith("component:"))) | .targetRef providesApis: .relations[] | select(.type == "providesApi" and (.targetRef | startswith("api:"))) | .targetRef consumesApis: .relations[] | select(.type == "consumesApi" and (.targetRef | startswith("api:"))) | .targetRef dependsOnComponent: .relations[] | select(.type == "dependsOn" and (.targetRef | startswith("component:"))) | .targetRef dependsOnResource: .relations[] | select(.type == "dependsOn" and (.targetRef | startswith("resource:"))) | .targetRef - kind: API selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"api"' properties: type: .spec.type lifecycle: .spec.lifecycle definition: .spec.definition | tostring definitionOpenAPI: if .spec.type == "open-api" then .spec.definition else null end definitionAsyncAPI: if .spec.type == "async-api" then .spec.definition else null end definitionGRPC: if .spec.type == "grpc" then .spec.definition else null end definitionGraphQL: if .spec.type == "graphql" then .spec.definition else null end description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef system: '"system:" + (.metadata.namespace // "default") + "/" + .spec.system' - kind: group selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"group"' properties: description: .metadata.description type: .metadata.type email: .metadata.email labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: parent: .relations[] | select(.type == "childOf" and (.targetRef | startswith("group:"))) | .targetRef members: .relations[] | select(.type == "hasMember" and (.targetRef | startswith("user:"))) | .targetRef - kind: user selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"user"' properties: email: .metadata.email description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags - kind: resource selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"resource"' properties: type: .spec.type description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef system: '"system:" + (.metadata.namespace // "default") + "/" + .spec.system' dependsOnResource: .relations[] | select(.type == "dependsOn" and (.targetRef | startswith("resource:"))) | .targetRef dependsOnComponent: .relations[] | select(.type == "dependsOn" and (.targetRef | startswith("component:"))) | .targetRef - kind: system selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"system"' properties: description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef domain: .relations[] | select(.type == "partOf" and (.targetRef | startswith("domain:"))) | .targetRef - kind: domain selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"domain"' properties: description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to 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 [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Additional examples of blueprints and the relevant integration configurations: ComponentAPIGroupUserResourceSystemDomain BlueprintJSON Create in Port ``` { "identifier": "component", "title": "Component", "icon": "Cloud", "schema": { "properties": { "type": { "title": "Type", "type": "string" }, "lifecycle": { "title": "Lifecycle", "type": "string" }, "language": { "type": "string", "title": "Language" }, "description": { "type": "string", "format": "markdown", "title": "Description" }, "labels": { "type": "object", "title": "Labels" }, "annotations": { "type": "object", "title": "Annotations" }, "links": { "type": "array", "items": { "format": "url", "type": "string" }, "title": "Links" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "calculationProperties": {} } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: component selector: query: "true" port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"component"' properties: type: .spec.type lifecycle: .spec.lifecycle language: .spec.language description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags ``` Mapping configurationYAML ``` - kind: API selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"api"' properties: type: .spec.type lifecycle: .spec.lifecycle definition: .spec.definition | tostring definitionOpenAPI: if .spec.type == "open-api" then .spec.definition else null end definitionAsyncAPI: if .spec.type == "async-api" then .spec.definition else null end definitionGRPC: if .spec.type == "grpc" then .spec.definition else null end definitionGraphQL: if .spec.type == "graphql" then .spec.definition else null end description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef system: '"system:" + (.metadata.namespace // "default") + "/" + .spec.system' ``` BlueprintJSON Create in Port ``` { "identifier": "group", "title": "Group", "icon": "TwoUsers", "schema": { "properties": { "type": { "title": "Type", "type": "string" }, "email": { "title": "Email", "type": "string", "format": "email" }, "description": { "type": "string", "format": "markdown", "title": "Description" }, "labels": { "type": "object", "title": "Labels" }, "annotations": { "type": "object", "title": "Annotations" }, "links": { "type": "array", "items": { "format": "url", "type": "string" }, "title": "Links" }, "tags": { "type": "array", "title": "Tags" } }, "required": [] }, "calculationProperties": {} } ``` Mapping configurationYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: group selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"group"' properties: description: .metadata.description type: .metadata.type email: .metadata.email labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags ``` Mapping configurationYAML ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"user"' properties: email: .metadata.email description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags ``` Mapping configurationYAML ``` - kind: resource selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"resource"' properties: type: .spec.type description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef system: '"system:" + (.metadata.namespace // "default") + "/" + .spec.system' dependsOnResource: .relations[] | select(.type == "dependsOn" and (.targetRef | startswith("resource:"))) | .targetRef dependsOnComponent: .relations[] | select(.type == "dependsOn" and (.targetRef | startswith("component:"))) | .targetRef ``` Mapping configurationYAML ``` - kind: system selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"system"' properties: description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef domain: .relations[] | select(.type == "partOf" and (.targetRef | startswith("domain:"))) | .targetRef ``` Mapping configurationYAML ``` - kind: domain selector: query: 'true' port: entity: mappings: identifier: .metadata.identifier title: .metadata.title // .metadata.name blueprint: '"domain"' properties: description: .metadata.description labels: .metadata.labels annotations: .metadata.annotations links: .metadata.links tags: .metadata.tags relations: owningUser: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("user:"))) | .targetRef owningGroup: .relations[] | select(.type == "ownedBy" and (.targetRef | startswith("group:"))) | .targetRef ``` ## Limitations[​](#limitations "Direct link to Limitations") Currently, the integration does not support [custom entity](https://backstage.io/docs/features/software-catalog/extending-the-model/#implementing-custom-model-extensions) kinds. --- # Slack Custom Ocean integration This integration was created using the [custom Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework.
Please note that: 1. This integration will not be listed in the `Connectors` page of your Port application, and must be installed manually using the instructions on this page. 2. This integration will not create components (e.g. `blueprints`, `mapping`, etc.) in your portal automatically, you will need to create them manually using the instructions on this page. Port's Slack integration allows you to model Slack resources in your software catalog and ingest data into them using the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) framework. ## Supported resources[​](#supported-resources "Direct link to Supported resources") The Slack integration can ingest the following resources into Port: * `slack-user` - Workspace users and their profile information from [`/users.list`](https://api.slack.com/methods/users.list). * `slack-channel` - Public and private channels from [`/conversations.list`](https://api.slack.com/methods/conversations.list). * `slack-user-group` - User groups and their memberships from [`/usergroups.list`](https://api.slack.com/methods/usergroups.list). It is possible to reference any field that appears in the API responses linked above in the mapping configuration. ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") To use this integration, you need: * A Slack Bot User OAuth Token with the following scopes: * `users:read` - View people in a workspace. * `channels:read` - View basic information about public channels. * `groups:read` - View basic information about private channels. * `usergroups:read` - View user groups in a workspace. **To create a Slack app and obtain an access token:** 1. Navigate to the [Slack API](https://api.slack.com/apps) and click **Create New App**. 2. Choose **From scratch** and provide a name for your app (e.g., "Port Integration"). 3. Select the workspace where you want to install the app. 4. In your app's settings, go to **OAuth & Permissions**. 5. Add the required scopes under **Bot Token Scopes**. 6. Click **Install App to Workspace** and authorize the app. 7. Copy the **Bot User OAuth Token** (starts with `xoxb-`) from the **OAuth & Permissions** page. Token security Store your OAuth token securely and never share it. The token provides access to your Slack workspace data. ## Installation[​](#installation "Direct link to Installation") The no-code Ocean custom integration can be installed in two ways: * **[Hosted by Port](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/hosted-by-port/.md)** — Port manages the infrastructure; configure the connection in the Port UI. * **[Self-hosted](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/setup/.md)** — Run the integration in your own infrastructure using Helm or Docker. Choose one of the following options: * Hosted by Port * Helm * Docker ### Set up in Port 1. Go to the [custom connector page](https://app.port.io/settings/data-sources?section=EXPORTERS\&provider=Custom) and open the no-code Ocean custom integration form. 2. Configure: * **Identifier**: `my-ocean-slack-integration`. * **Base URL**: `https://slack.com/api`. * **Authentication type**: `bearer_token`. * **Token**: your Slack Bot User OAuth Token (starts with `xoxb-`). 3. Expand **Advanced configuration** and set: * **Pagination type**: `cursor`. * **Pagination parameter**: `cursor`. * **Cursor path**: `response_metadata.next_cursor`. * **Has more path**: `response_metadata.next_cursor`. 4. Click **Save & install**. Continue with the mappings After the integration is installed, continue to the **Configuration** section on this page and add the resource mappings. ## Prerequisites * A Kubernetes cluster - the integration's container chart will be deployed to this cluster. * [`kubectl`](https://kubernetes.io/docs/tasks/tools/#kubectl) and [`helm`](https://helm.sh/) must be installed on your machine. Your `kubectl` CLI must be connected to the Kubernetes cluster where you plan to install the integration. ## Installation 1. Add Port's Helm repo and install the no-code Ocean custom integration: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_SLACK_BOT_TOKEN`. ``` helm repo add --force-update port-labs https://port-labs.github.io/helm-charts helm upgrade --install my-ocean-slack-integration port-labs/port-ocean \ --set port.clientId="YOUR_PORT_CLIENT_ID" \ --set port.clientSecret="YOUR_PORT_CLIENT_SECRET" \ --set port.baseUrl="https://api.port.io" \ --set initializePortResources=true \ --set scheduledResyncInterval=60 \ --set integration.identifier="my-ocean-slack-integration" \ --set integration.type="custom" \ --set integration.eventListener.type="POLLING" \ --set integration.config.baseUrl="https://slack.com/api" \ --set integration.config.authType="bearer_token" \ --set integration.config.apiToken="YOUR_SLACK_BOT_TOKEN" \ --set integration.config.paginationType="cursor" \ --set integration.config.paginationParam="cursor" \ --set integration.config.cursorPath="response_metadata.next_cursor" \ --set integration.config.hasMorePath="response_metadata.next_cursor" ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` ## Configuration parameters This table summarizes the available parameters for the installation. | Parameter | Description | Example | Required | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------- | | `port.clientId` | Your Port [client id](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.clientSecret` | Your Port [client secret](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/api/ingest-via-api-overview#find-your-port-credentials) | | ✅ | | `port.baseUrl` | Your Port API URL - `https://api.port.io` for EU, `https://api.us.port.io` for US | | ✅ | | `integration.config.baseUrl` | The base URL of the Slack API instance | | ✅ | | `integration.config.authType` | The authentication type for the API (use `bearer_token` for Slack) | bearer\_token | ✅ | | `integration.config.apiToken` | Your Slack Bot User OAuth Token (starts with `xoxb-`) | xoxb-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | ✅ | | `integration.config.paginationType` | How your API handles pagination (offset, page, cursor, or none) | cursor | ✅ | | `integration.config.paginationParam` | The query parameter name for the cursor (Slack API uses `cursor`) | cursor | ✅ | | `integration.config.cursorPath` | JQ expression pointing to the cursor field in the response | response\_metadata.next\_cursor | ✅ | | `integration.config.hasMorePath` | Path to check if there are more pages. | response\_metadata.next\_cursor | ✅ | | `integration.eventListener.type` | The event listener type. Read more about [event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) | POLLING | ✅ | | `integration.type` | The integration type (must be `custom` for the no-code Ocean custom integration). | custom | ✅ | | `integration.identifier` | Unique identifier for the integration instance | my-ocean-slack-integration | ✅ | | `scheduledResyncInterval` | The number of minutes between each resync. When not set the integration will resync for each event listener resync event. Read more about [scheduledResyncInterval](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) | 60 | ❌ | | `initializePortResources` | Default true, When set to true the integration will create default blueprints and the port App config Mapping. | true | ❌ | | `sendRawDataExamples` | Enable sending raw data examples from the third party API to port for testing and managing the integration mapping. Default is true | true | ❌ |
Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). To run the integration using Docker for a one-time sync: Replace placeholders Remember to replace the placeholders for `YOUR_PORT_CLIENT_ID`, `YOUR_PORT_CLIENT_SECRET`, and `YOUR_SLACK_BOT_TOKEN`. ``` docker run -i --rm --platform=linux/amd64 \ -e OCEAN__EVENT_LISTENER='{"type":"ONCE"}' \ -e OCEAN__INITIALIZE_PORT_RESOURCES=true \ -e OCEAN__SEND_RAW_DATA_EXAMPLES=true \ -e OCEAN__INTEGRATION__CONFIG__BASE_URL="https://slack.com/api" \ -e OCEAN__INTEGRATION__CONFIG__AUTH_TYPE="bearer_token" \ -e OCEAN__INTEGRATION__CONFIG__API_TOKEN="YOUR_SLACK_BOT_TOKEN" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_TYPE="cursor" \ -e OCEAN__INTEGRATION__CONFIG__PAGINATION_PARAM="cursor" \ -e OCEAN__INTEGRATION__CONFIG__CURSOR_PATH="response_metadata.next_cursor" \ -e OCEAN__INTEGRATION__CONFIG__HAS_MORE_PATH="response_metadata.next_cursor" \ -e OCEAN__PORT__CLIENT_ID="YOUR_PORT_CLIENT_ID" \ -e OCEAN__PORT__CLIENT_SECRET="YOUR_PORT_CLIENT_SECRET" \ -e OCEAN__PORT__BASE_URL="https://api.port.io" \ ghcr.io/port-labs/port-ocean-custom:latest ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://app.us.port.io)) → `https://api.us.port.io` Advanced integration configuration For advanced configuration such as proxies or self-signed certificates, see [Ocean advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## Set up data model[​](#set-up-data-model "Direct link to Set up data model") Before the integration can sync data, you need to create the required blueprints in Port. These blueprints define the data model for your Slack resources. **To create the blueprints:** 1. Go to the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Click on the `+ Blueprint` button. 3. Copy and paste each blueprint JSON from the sections below. **Slack User Blueprint (click to expand)** Workspace users and their profile information: Create in Port ``` { "identifier": "slack-user", "description": "A Slack workspace user", "title": "Slack User", "icon": "Slack", "schema": { "properties": { "name": { "title": "Username", "type": "string" }, "realName": { "title": "Real Name", "type": "string" }, "displayName": { "title": "Display Name", "type": "string" }, "email": { "title": "Email", "type": "string", "format": "email" }, "title": { "title": "Job Title", "type": "string" }, "phone": { "title": "Phone", "type": "string" }, "statusText": { "title": "Status Text", "type": "string" }, "statusEmoji": { "title": "Status Emoji", "type": "string" }, "isAdmin": { "title": "Is Admin", "type": "boolean" }, "isOwner": { "title": "Is Owner", "type": "boolean" }, "isBot": { "title": "Is Bot", "type": "boolean" }, "isDeleted": { "title": "Is Deleted", "type": "boolean" }, "timezone": { "title": "Timezone", "type": "string" }, "timezoneLabel": { "title": "Timezone Label", "type": "string" }, "updated": { "title": "Last Updated", "type": "string", "format": "date-time" } }, "required": ["name"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Slack Channel Blueprint (click to expand)** Public and private channels: Create in Port ``` { "identifier": "slack-channel", "description": "A Slack channel (public or private)", "title": "Slack Channel", "icon": "Slack", "schema": { "properties": { "name": { "title": "Channel Name", "type": "string" }, "topic": { "title": "Topic", "type": "string" }, "purpose": { "title": "Purpose", "type": "string" }, "isPrivate": { "title": "Is Private", "type": "boolean" }, "isArchived": { "title": "Is Archived", "type": "boolean" }, "isGeneral": { "title": "Is General", "type": "boolean" }, "isMember": { "title": "Is Member", "type": "boolean" }, "memberCount": { "title": "Member Count", "type": "number" }, "created": { "title": "Created Date", "type": "string", "format": "date-time" }, "updated": { "title": "Last Updated", "type": "string", "format": "date-time" } }, "required": ["name"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "creator": { "title": "Creator", "target": "slack-user", "required": false, "many": false } } } ``` **Slack User Group Blueprint (click to expand)** User groups and their memberships: Create in Port ``` { "identifier": "slack-user-group", "description": "A Slack user group", "title": "Slack User Group", "icon": "Slack", "schema": { "properties": { "name": { "title": "Name", "type": "string" }, "handle": { "title": "Handle", "type": "string" }, "description": { "title": "Description", "type": "string" }, "isExternal": { "title": "Is External", "type": "boolean" }, "dateCreate": { "title": "Date Created", "type": "number" }, "dateUpdate": { "title": "Date Updated", "type": "number" }, "updatedBy": { "title": "Updated By", "type": "string" } }, "required": ["name"] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "members": { "title": "Members", "target": "slack-user", "required": false, "many": true } } } ``` 4. Click `Save` to save the blueprint. ## Configuration[​](#configuration "Direct link to Configuration") After installation, define which endpoints to sync in your integration configuration. Each resource maps an API endpoint to Port entities using [JQ expressions](https://stedolan.github.io/jq/manual/) to transform the data. **Key mapping components:** * **`kind`**: The API endpoint path (combined with your base URL). * **`selector.query`**: JQ filter to include/exclude entities (use `'true'` to sync all). * **`selector.data_path`**: JQ expression pointing to the array of items in the response. * **`port.entity.mappings`**: How to map API fields to Port entity properties. For more details on how the no-code Ocean custom integration works, see the [How it works](https://docs.port.io/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview#how-it-works) section in the custom integration overview. **Slack API response format:** Slack API responses typically follow this structure: ``` { "ok": true, "members": [...], "response_metadata": { "next_cursor": "dXNlcjpVMEc5V0ZYTlo=" } } ``` The actual data array is usually at the root level (e.g., `members`, `channels`, `usergroups`), and pagination information is in `response_metadata.next_cursor`. User Groups require include\_users parameter The Slack API's `usergroups.list` endpoint does not return the `users` field by default. To populate the `members` relation, you must include `include_users: "true"` in the `query_params` section of your mapping configuration. **To configure the mappings:** 1. Go to your [connectors page](https://app.port.io/settings/data-sources). 2. Find your Slack integration in the list. 3. Click on the integration to open the mapping editor. 4. Add the resource mapping configurations below. **Slack Users mapping (click to expand)** ``` resources: - kind: /users.list selector: query: 'true' data_path: '.members' query_params: limit: "200" port: entity: mappings: identifier: .id title: .real_name // .name // .id blueprint: '"slack-user"' properties: name: .name realName: .real_name displayName: .profile.display_name email: .profile.email title: .profile.title phone: .profile.phone statusText: .profile.status_text statusEmoji: .profile.status_emoji isAdmin: .is_admin isOwner: .is_owner isBot: .is_bot isDeleted: .deleted timezone: .tz timezoneLabel: .tz_label ``` **Slack Channels mapping (click to expand)** ``` resources: - kind: /conversations.list selector: query: 'true' data_path: '.channels' query_params: types: "public_channel,private_channel" limit: "200" port: entity: mappings: identifier: .id title: .name blueprint: '"slack-channel"' properties: name: .name topic: .topic.value purpose: .purpose.value isPrivate: .is_private isArchived: .is_archived isGeneral: .is_general isMember: .is_member memberCount: .num_members relations: creator: .creator ``` **Slack User Groups mapping (click to expand)** ``` resources: - kind: /usergroups.list selector: query: 'true' data_path: '.usergroups' query_params: include_users: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"slack-user-group"' properties: name: .name handle: .handle description: .description isExternal: .is_external dateCreate: .date_create dateUpdate: .date_update updatedBy: .updated_by relations: members: .users[] ``` 5. Click `Save` to save the mapping. ## Customization[​](#customization "Direct link to Customization") If you want to customize your setup or test different API endpoints before committing to a configuration, use the [interactive builder](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/installation-types/self-hosted/build-your-integration/.md). **The interactive builder helps you:** 1. Test your Slack API endpoints with live data. 2. Automatically detect the data structure and field types. 3. Generate blueprints and resource mappings tailored to your preferences. 4. Get installation commands with your configuration pre-filled. Simply provide your Slack API details, and the builder will generate everything you need to install and create the integration in Port. --- # Project management Port's project management integrations make it easy to keep your software catalog up to date based on information stored in your preferred project management tool. For more information, check out the documentation for your respective tool: ## [🗃Jira](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/.md) [2 items](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/.md) ## [🗃Jira Server](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/.md) [1 item](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/.md) ## [📄️Linear](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/linear/.md) [Sync Linear teams, issues, labels, documents, users, projects, initiatives, workflow states, cycles, and team memberships into Port's software catalog.](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/linear/.md) --- # Jira Server (self-hosted) ![Jira icon](/img/guides/icons/Jira.svg)![Jira icon](/img/guides/icons/Jira.svg) Loading version... Port's Jira Server integration allows you to model Jira Server resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/project-getAllProjects)[User](https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/user/search-findUsers)[Issue](https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/search-search) ## Core support [Live eventsSee supported resources](#set-up-live-events) Jira Server Support This integration is specifically designed for **Jira Server (Self-Hosted)** installations. For Jira Cloud, use [Port's Jira Cloud integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/.md). ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Self-hosted2Prerequisites3Install Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Prerequisites ## Authentication[​](#authentication "Direct link to Authentication") The Jira Server integration supports two authentication methods: Token authentication recommended We recommend using Personal Access Token authentication for better security and easier management. ### Personal Access Token (Recommended)[​](#personal-access-token-recommended "Direct link to Personal Access Token (Recommended)") Personal Access Tokens provide a more secure authentication method and are the recommended approach. 1. **Create a Personal Access Token in Jira Server:** * Log in to your Jira Server as an administrator * Go to **Jira Administration** → **System** → **User Management** * Select the user account that will be used for the integration * Click **Personal Access Tokens** in the left menu * Click **Create token** * Enter a name for the token and click **Create** * **Important:** Copy the token value immediately as it won't be shown again 2. **Configure the integration:** * Use the `token` parameter in your integration configuration * Do not provide `username` and `password` when using token authentication ### Basic Authentication (Username/Password)[​](#basic-authentication-usernamepassword "Direct link to Basic Authentication (Username/Password)") Basic authentication uses a username and password combination. 1. **Use an existing Jira user account:** * Ensure the user has appropriate permissions to read projects, issues, and users * The user should have **Browse Projects** and **Browse Users** permissions 2. **Configure the integration:** * Use the `username` and `password` parameters in your integration configuration * Do not provide the `token` parameter when using basic authentication ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. For the default mapping configuration and blueprints that come with this integration, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/examples/.md#defaults). ### JQL support for issues[​](#jql-support-for-issues "Direct link to JQL support for issues") The Ocean Jira Server integration supports querying objects from the `issue` kind using [JQL (Jira Query Language)](https://www.atlassian.com/software/jira/guides/jql/overview), making it possible to specifically filter the issues that are queried from Jira Server and ingested to Port. To use JQL filtering, add to the `selector` object a `jql` key with your desired JQL query as the value. For example: ``` resources: - kind: issue # JQL filtering can only be used with the "issue" kind selector: query: "true" # JQ boolean expression. If evaluated to false - this object will be skipped. jql: "status != Done" # JQL query, will only ingest issues whose status is not "Done" port: ``` ### Default mapping configuration[​](#default-mapping-configuration "Direct link to 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: '"jiraServerProject"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key - kind: user selector: query: "true" port: entity: mappings: identifier: .key title: .displayName blueprint: '"jiraServerUser"' properties: emailAddress: .emailAddress active: .active timeZone: .timeZone locale: .locale avatarUrl: .avatarUrls["48x48"] - kind: issue selector: query: "true" # jql: 'project = "KEY"' # Optional: Filter issues using JQL port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraServerIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name components: .fields.components | map(.name) creator: .fields.creator.displayName priority: .fields.priority.name labels: .fields.labels created: .fields.created updated: .fields.updated resolutionDate: .fields.resolutiondate relations: project: .fields.project.key assignee: .fields.assignee.key reporter: .fields.reporter.key ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to 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 [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Additional examples of blueprints and the relevant integration configurations can be found on the jira server [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/examples/.md) ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * Self-hosted ### BaseUrl & webhook configuration[​](#baseurl--webhook-configuration "Direct link to 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 Jira Server 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 Jira Server 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Jira Server is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Jira Server can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ## Defaults[​](#defaults "Direct link to Defaults") ### Default blueprints[​](#default-blueprints "Direct link to Default blueprints") The integration creates the following default blueprints: **Jira Server Project blueprint (click to expand)** Create in Port ``` { "identifier": "jiraServerProject", "title": "Jira Server Project", "icon": "Jira", "description": "A Jira project", "schema": { "properties": { "url": { "title": "Project URL", "type": "string", "format": "url", "description": "URL to the project in Jira" } } }, "calculationProperties": {} } ``` **Jira Server User blueprint (click to expand)** Create in Port ``` { "identifier": "jiraServerUser", "title": "Jira Server User", "icon": "User", "description": "A Jira user account", "schema": { "properties": { "emailAddress": { "title": "Email", "type": "string", "format": "email", "description": "User's email address" }, "active": { "title": "Active Status", "type": "boolean", "description": "Whether the user account is active" }, "timeZone": { "title": "Time Zone", "type": "string", "description": "User's configured time zone" }, "locale": { "title": "Locale", "type": "string", "description": "User's configured locale" }, "avatarUrl": { "title": "Avatar URL", "type": "string", "format": "url", "description": "URL for user's 48x48 avatar image" } } }, "relations": {}, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {} } ``` **Jira Server Issue blueprint (click to expand)** Create in Port ``` { "identifier": "jiraServerIssue", "title": "Jira Server Issue", "icon": "Jira", "schema": { "properties": { "url": { "title": "Issue URL", "type": "string", "format": "url", "description": "URL to the issue in Jira" }, "status": { "title": "Status", "type": "string", "description": "The status of the issue" }, "issueType": { "title": "Type", "type": "string", "description": "The type of the issue" }, "components": { "title": "Components", "type": "array", "items": { "type": "string" }, "description": "The components related to this issue" }, "creator": { "title": "Creator", "type": "string", "description": "The user that created the issue", "format": "user" }, "priority": { "title": "Priority", "type": "string", "description": "The priority of the issue" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "created": { "title": "Created At", "type": "string", "description": "The created datetime of the issue", "format": "date-time" }, "updated": { "title": "Updated At", "type": "string", "description": "The updated datetime of the issue", "format": "date-time" }, "resolutionDate": { "title": "Resolved At", "type": "string", "description": "The datetime the issue changed to a resolved state", "format": "date-time" } } }, "calculationProperties": {}, "mirrorProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "jiraServerProject", "title": "Project", "description": "The Jira project that contains this issue", "required": false, "many": false }, "assignee": { "target": "jiraServerUser", "title": "Assignee", "required": false, "many": false }, "reporter": { "target": "jiraServerUser", "title": "Reporter", "required": false, "many": false } } } ``` ### Default mapping configuration[​](#default-mapping-configuration "Direct link to 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: '"jiraServerProject"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key - kind: user selector: query: "true" port: entity: mappings: identifier: .key title: .displayName blueprint: '"jiraServerUser"' properties: emailAddress: .emailAddress active: .active timeZone: .timeZone locale: .locale avatarUrl: .avatarUrls["48x48"] - kind: issue selector: query: "true" # jql: 'project = "KEY"' # Optional: Filter issues using JQL port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraServerIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name components: .fields.components | map(.name) creator: .fields.creator.displayName priority: .fields.priority.name labels: .fields.labels created: .fields.created updated: .fields.updated resolutionDate: .fields.resolutiondate relations: project: .fields.project.key assignee: .fields.assignee.key reporter: .fields.reporter.key ``` --- # Jira ![Jira icon](/img/guides/icons/Jira.svg)![Jira icon](/img/guides/icons/Jira.svg) Loading version... Port's Jira Cloud integration allows you to model Jira Cloud resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Project](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-projects/#api-rest-api-3-project-search-get)[User](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-users/#api-group-users)[Issue](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-jql-get)[Team](https://developer.atlassian.com/platform/teams/rest/v1/api-group-teams-public-api/#api-group-teams-public-api)[Release](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-project-versions/#api-rest-api-3-project-projectidorkey-versions-get)[Epic](https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-epic-get)[Sprint](https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-boardid-sprint-get)[Board](https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-agile-1-0-board-get)[Worklog](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-worklogs/#api-rest-api-3-issue-issueidorkey-worklog-get)[Backlog](https://developer.atlassian.com/cloud/jira/software/rest/api-group-board/#api-rest-software-1-0-board-boardid-backlog-get)[Component](https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-project-components/#api-rest-api-2-project-projectidorkey-component-get) ## Core support [Live eventsSee supported resources](#set-up-live-events) Jira Cloud integration This integration is designed for `Jira Cloud`. For `Jira Server` (self-hosted), use [Port's Jira Server Ocean integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/.md). ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods: Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port with OAuth2Install Hosted by Port with OAuth * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Fast OAuth setup - no API token required * ✧Great for getting started, not recommended for production environments Hosted by Port with custom settings * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` deleteDependentEntities: true createMissingRelatedEntities: true enableMergeEntity: true resources: - kind: user selector: query: 'true' port: entity: mappings: identifier: .accountId title: .displayName blueprint: '"jiraUser"' properties: emailAddress: .emailAddress displayName: .displayName active: .active accountType: .accountType - kind: project selector: query: 'true' port: entity: mappings: identifier: .key title: .name blueprint: '"jiraProject"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key - kind: issue selector: query: 'true' jql: (statusCategory != Done) OR (created >= -1w) OR (updated >= -1w) port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name components: .fields.components creator: .fields.creator.emailAddress priority: .fields.priority.name labels: .fields.labels created: .fields.created updated: .fields.updated resolutionDate: .fields.resolutiondate relations: project: .fields.project.key parentIssue: .fields.parent.key subtasks: .fields.subtasks | map(.key) jira_user_assignee: .fields.assignee.accountId jira_user_reporter: .fields.reporter.accountId assignee: combinator: '"or"' rules: - property: '"jira_user_id"' operator: '"="' value: .fields.assignee.accountId - property: '"$identifier"' operator: '"="' value: .fields.assignee.email reporter: combinator: '"or"' rules: - property: '"jira_user_id"' operator: '"="' value: .fields.reporter.accountId - property: '"$identifier"' operator: '"="' value: .fields.reporter.email - kind: issue selector: query: 'true' jql: (statusCategory != Done) OR (created >= -1w) OR (updated >= -1w) port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' relations: pull_request: combinator: '"and"' rules: - property: '"$title"' operator: '"contains"' value: .key ``` ## Capabilities[​](#capabilities "Direct link to Capabilities") See the [capabilities page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/capabilities/.md) for more configuration options and examples. ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/examples/.md). UserProjectIssueTeamReleaseBacklogWorklogBoardSprintEpicComponent Default mappingYAML ``` - kind: user selector: query: 'true' port: entity: mappings: identifier: .accountId title: .displayName blueprint: '"jiraUser"' properties: emailAddress: .emailAddress displayName: .displayName active: .active accountType: .accountType ``` Sample payloadJSON ``` { "accountId": "U-PA", "accountType": "app", "active": true, "avatarUrls": { "16x16": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png", "24x24": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png", "32x32": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png", "48x48": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png" }, "displayName": "AI", "key": "", "name": "", "self": "https://your-domain.atlassian.net/rest/api/3/user?accountId=U-PA" }, { "accountId": "U-PA-2", "accountType": "app", "active": true, "avatarUrls": { "16x16": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png", "24x24": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png", "32x32": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png", "48x48": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png" }, "displayName": "AI-2", "key": "", "name": "", "self": "https://your-domain.atlassian.net/rest/api/3/user?accountId=U-PA-2" } ``` Port entityJSON ``` { "identifier": "U-PA", "title": "AI", "team": [], "properties": { "accountId": "U-PA", "displayName": "AI", "active": true, "accountType": "app", "avatarUrl": "https://secure.gravatar.com/avatar/ab3787cd0c16633ae050dff9d5ab15fc?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FS-0.png" }, "relations": {}, "icon": "User" } ``` Default mappingYAML ``` - kind: project selector: query: 'true' port: entity: mappings: identifier: .key title: .name blueprint: '"jiraProject"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key ``` Sample payloadJSON ``` { "expand": "description,lead,issueTypes,url,projectKeys,permissions,insight", "self": "https://myaccount.atlassian.net/rest/api/3/project/10000", "id": "10000", "key": "PA", "name": "Port-AI", "avatarUrls": { "48x48": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413", "24x24": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=small", "16x16": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=xsmall", "32x32": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=medium" }, "projectTypeKey": "software", "simplified": true, "style": "next-gen", "isPrivate": false, "properties": {}, "entityId": "7f4f8d6f-705b-4074-84be-46f0d012cd8e", "uuid": "7f4f8d6f-705b-4074-84be-46f0d012cd8e" } ``` Port entityJSON ``` { "identifier": "PA", "title": "Port-AI", "icon": "Jira", "blueprint": "jiraProject", "team": [], "properties": { "url": "https://myaccount.atlassian.net/projects/PA", "totalIssues": 100 }, "relations": {}, "createdAt": "2023-11-06T11:22:05.433Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-11-06T11:22:05.433Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `expand` | `string` | Comma-separated list of additional project properties to expand in the response. Supported values include 'description', 'lead', 'url', 'projectKeys', and 'insight'. Default: `"insight"`. | Default mappingYAML ``` - kind: issue selector: query: 'true' jql: (statusCategory != Done) OR (created >= -1w) OR (updated >= -1w) port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name components: .fields.components creator: .fields.creator.emailAddress priority: .fields.priority.name labels: .fields.labels created: .fields.created updated: .fields.updated resolutionDate: .fields.resolutiondate relations: project: .fields.project.key parentIssue: .fields.parent.key subtasks: .fields.subtasks | map(.key) jira_user_assignee: .fields.assignee.accountId jira_user_reporter: .fields.reporter.accountId assignee: combinator: '"or"' rules: - property: '"jira_user_id"' operator: '"="' value: .fields.assignee.accountId - property: '"$identifier"' operator: '"="' value: .fields.assignee.email reporter: combinator: '"or"' rules: - property: '"jira_user_id"' operator: '"="' value: .fields.reporter.accountId - property: '"$identifier"' operator: '"="' value: .fields.reporter.email - kind: issue selector: query: 'true' jql: (statusCategory != Done) OR (created >= -1w) OR (updated >= -1w) port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' relations: pull_request: combinator: '"and"' rules: - property: '"$title"' operator: '"contains"' value: .key ``` Sample payloadJSON ``` { "expand": "operations,versionedRepresentations,editmeta,changelog,customfield_10010.requestTypePractice,renderedFields", "id": "10000", "self": "https://myaccount.atlassian.net/rest/api/3/issue/10000", "key": "PA-1", "fields": { "statuscategorychangedate": "2023-11-06T11:02:59.341+0000", "issuetype": { "self": "https://myaccount.atlassian.net/rest/api/3/issuetype/10001", "id": "10001", "description": "Tasks track small, distinct pieces of work.", "iconUrl": "https://myaccount.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10318?size=medium", "name": "Task", "subtask": false, "avatarId": 10318, "entityId": "a7309bf9-70c5-4237-bdaf-0261037b6ecc", "hierarchyLevel": 0 }, "timespent": "None", "customfield_10030": "None", "project": { "self": "https://myaccount.atlassian.net/rest/api/3/project/10000", "id": "10000", "key": "PA", "name": "Port-AI", "projectTypeKey": "software", "simplified": true, "avatarUrls": { "48x48": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413", "24x24": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=small", "16x16": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=xsmall", "32x32": "https://myaccount.atlassian.net/rest/api/3/universal_avatar/view/type/project/avatar/10413?size=medium" } }, "customfield_10031": "None", "customfield_10032": "None", "fixVersions": [], "aggregatetimespent": "None", "resolution": "None", "customfield_10027": "None", "customfield_10028": "None", "customfield_10029": "None", "resolutiondate": "None", "workratio": -1, "watches": { "self": "https://myaccount.atlassian.net/rest/api/3/issue/PA-1/watchers", "watchCount": 1, "isWatching": true }, "lastViewed": "None", "created": "2023-11-06T11:02:59.000+0000", "customfield_10020": "None", "customfield_10021": "None", "customfield_10022": "None", "priority": { "self": "https://myaccount.atlassian.net/rest/api/3/priority/3", "iconUrl": "https://myaccount.atlassian.net/images/icons/priorities/medium.svg", "name": "Medium", "id": "3" }, "customfield_10023": "None", "customfield_10024": "None", "customfield_10025": "None", "labels": ["infra"], "customfield_10026": "None", "customfield_10016": "None", "customfield_10017": "None", "customfield_10018": { "hasEpicLinkFieldDependency": false, "showField": false, "nonEditableReason": { "reason": "PLUGIN_LICENSE_ERROR", "message": "The Parent Link is only available to Jira Premium users." } }, "customfield_10019": "0|hzzzzz:", "timeestimate": "None", "aggregatetimeoriginalestimate": "None", "versions": [], "issuelinks": [], "assignee": { "self": "https://myaccount.atlassian.net/rest/api/3/user?accountId=712020%3A05acda87-42da-44d8-b21e-f71a508e5d11", "accountId": "712020:05acda87-42da-44d8-b21e-f71a508e5d11", "emailAddress": "username@example.com.io", "avatarUrls": { "48x48": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "24x24": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "16x16": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "32x32": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png" }, "displayName": "User Name", "active": true, "timeZone": "UTC", "accountType": "atlassian" }, "updated": "2023-11-06T11:03:18.244+0000", "status": { "self": "https://myaccount.atlassian.net/rest/api/3/status/10000", "description": "", "iconUrl": "https://myaccount.atlassian.net/", "name": "To Do", "id": "10000", "statusCategory": { "self": "https://myaccount.atlassian.net/rest/api/3/statuscategory/2", "id": 2, "key": "new", "colorName": "blue-gray", "name": "To Do" } }, "components": [], "timeoriginalestimate": "None", "description": "None", "customfield_10010": "None", "customfield_10014": "None", "customfield_10015": "None", "customfield_10005": "None", "customfield_10006": "None", "security": "None", "customfield_10007": "None", "customfield_10008": "None", "aggregatetimeestimate": "None", "customfield_10009": "None", "summary": "Setup infra", "creator": { "self": "https://myaccount.atlassian.net/rest/api/3/user?accountId=712020%3A05acda87-42da-44d8-b21e-f71a508e5d11", "accountId": "712020:05acda87-42da-44d8-b21e-f71a508e5d11", "emailAddress": "username@example.com.io", "avatarUrls": { "48x48": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "24x24": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "16x16": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "32x32": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png" }, "displayName": "User Name", "active": true, "timeZone": "UTC", "accountType": "atlassian" }, "subtasks": [], "reporter": { "self": "https://myaccount.atlassian.net/rest/api/3/user?accountId=712020%3A05acda87-42da-44d8-b21e-f71a508e5d11", "accountId": "712020:05acda87-42da-44d8-b21e-f71a508e5d11", "emailAddress": "username@example.com.io", "avatarUrls": { "48x48": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "24x24": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "16x16": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png", "32x32": "https://secure.gravatar.com/avatar/0d5d34ceb820d324d69046a1b2f51dc0?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FIC-3.png" }, "displayName": "User Name", "active": true, "timeZone": "UTC", "accountType": "atlassian" }, "aggregateprogress": { "progress": 0, "total": 0 }, "customfield_10001": "None", "customfield_10002": "None", "customfield_10003": "None", "customfield_10004": "None", "environment": "None", "duedate": "None", "progress": { "progress": 0, "total": 0 }, "votes": { "self": "https://myaccount.atlassian.net/rest/api/3/issue/PA-1/votes", "votes": 0, "hasVoted": false } } } ``` Port entityJSON ``` { "identifier": "PA-1", "title": "Setup infra", "icon": null, "blueprint": "jiraIssue", "team": [], "properties": { "url": "https://myaccount.atlassian.net/browse/PA-1", "status": "To Do", "issueType": "Task", "components": [], "creator": "username@example.com.io", "priority": "Medium", "created": "2023-11-06T11:02:59.000+0000", "updated": "2023-11-06T11:03:18.244+0000" }, "relations": { "parentIssue": null, "project": "PA", "assignee": "username@example.com.io", "reporter": "username@example.com.io", "subtasks": [] }, "createdAt": "2023-11-06T11:22:07.550Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2023-11-06T11:22:07.550Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `jql` | `string` | JQL (Jira Query Language) expression used to filter which issues are synced. Defaults to open issues and those created or updated within the last week. Default: `"(statusCategory != Done) OR (created >= -1w) OR (updated >= -1w)"`. | | `fields` | `string` | Comma-separated list of issue fields to return. Use '\*all' to return all fields, or specify individual field names (e.g. 'summary,status,assignee') to reduce payload size. Default: `"*all"`. | | `expand` | `string` | Comma-separated list of additional issue properties to expand. Supported values include 'renderedFields', 'names', 'schema', 'transitions', 'operations', 'editmeta', and 'changelog'. | Default mappingYAML ``` - kind: team selector: query: "true" includeMembers: true port: entity: mappings: identifier: .teamId title: .displayName blueprint: '"jiraTeam"' properties: organizationId: .organizationId teamType: .teamType description: .description relations: members: if .__members != null then .__members | map(.accountId) else [] end ``` Sample payloadJSON ``` { "description": "Port-AI", "displayName": "AI", "organizationId": "10000", "teamId": "T-PA", "teamType": "OPEN" }, { "description": "Port-AI", "displayName": "AI-2", "organizationId": "10000", "teamId": "T-PA-2", "teamType": "OPEN" } ``` Port entityJSON ``` { "identifier": "T-PA", "title": "Port-AI", "icon": "Users", "properties": { "organizationId": "10000", "teamType": "OPEN", "description": "Port-AI" }, "relations": { "members": [ "U-PA", "U-PA-2" ] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {} } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `includeMembers` | `boolean` | Whether to fetch and include the list of members for each team. Enabling this will make additional API calls and may increase sync time for large teams. Default: `false`. | Default mappingYAML ``` - kind: release selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraRelease"' properties: description: .description released: .released archived: .archived releaseDate: .releaseDate overdue: .overdue relations: project: .__projectKey ``` Sample payloadJSON ``` { "self": "https://your-domain.atlassian.net/rest/api/3/version/10000", "id": "10000", "name": "New Version 1", "description": "An excellent version", "archived": false, "released": true, "releaseDate": "2010-07-06", "userReleaseDate": "6/Jul/2010", "overdue": true, "projectId": 10000, "__projectKey": "PR" } ``` Port entityJSON ``` { "identifier": "10000", "title": "New Version 1", "icon": "Jira", "blueprint": "jiraRelease", "properties": { "description": "An excellent version", "released": true, "archived": false, "releaseDate": "2010-07-06", "overdue": true }, "relations": { "project": "PR" }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {} } ``` Default mappingYAML ``` resources: - kind: backlog selector: query: 'true' jql: statusCategory != Done port: # Maps to jiraIssue blueprint; uses enableMergeEntity so the board relation # is merged onto existing issue entities ingested by the issue kind without duplication. entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name priority: .fields.priority.name labels: .fields.labels components: '[.fields.components[]?.name]' created: .fields.created updated: .fields.updated resolutionDate: .fields.resolutiondate creator: .fields.creator.accountId dueDate: .fields.duedate storyPoints: .fields.customfield_10026 // null team: .fields.customfield_10001.name // null sprint: .fields.customfield_10020 | if . != null and length > 0 then .[-1].name else null end sprintHistory: '[.fields.customfield_10020[]?.name] | if length > 0 then . else null end' relations: project: .fields.project.key parentIssue: (.fields.parent // null) | if . != null then .key else null end board: '.__boardId | tostring' jira_user_assignee: .fields.assignee.accountId // null jira_user_reporter: .fields.reporter.accountId // null ``` Sample payloadJSON ``` { "expand": "schema,names", "issues": [ { "id": "20115", "key": "PORT-9446", "self": "https://your-domain.atlassian.net/rest/agile/1.0/issue/20115", "fields": { "summary": "K8s Exporter | Don't fail initialization on existing blueprints", "status": { "name": "To Do", "id": "10000", "statusCategory": { "key": "new", "name": "To Do" } }, "issuetype": { "name": "Task", "subtask": false, "hierarchyLevel": 0 }, "priority": { "name": "Must Have", "id": "10002" }, "labels": [], "components": [], "created": "2024-07-27T14:22:00.989+0300", "updated": "2025-08-03T09:32:44.267+0300", "resolutiondate": null, "creator": { "accountId": "712020:5df2157d-063f-4a8a-89d1-1b8e6035ec11", "displayName": "Tom Tankilevitch" }, "project": { "key": "PORT", "name": "Port" }, "parent": null, "sprint": null, "flagged": false } } ], "nextPageToken": "CpEB...", "isLast": false } ``` Port entityJSON ``` { "identifier": "PORT-9446", "title": "K8s Exporter | Don't fail initialization on existing blueprints", "blueprint": "jiraIssue", "properties": { "url": "https://your-domain.atlassian.net/browse/PORT-9446", "status": "To Do", "issueType": "Task", "priority": "Must Have", "labels": [], "components": [], "creator": "712020:5df2157d-063f-4a8a-89d1-1b8e6035ec11", "created": "2024-07-27T14:22:00.989+0300", "updated": "2025-08-03T09:32:44.267+0300", "resolutionDate": null }, "relations": { "project": "PORT", "parentIssue": null, "board": "1" } } ``` Default mappingYAML ``` resources: - kind: worklog selector: query: 'true' jql: updated >= -1w port: entity: mappings: identifier: .id title: .author.displayName + " - " + .started blueprint: '"jiraWorklog"' properties: timeSpent: .timeSpent timeSpentSeconds: .timeSpentSeconds started: .started created: .created updated: .updated authorAccountId: .author.accountId authorDisplayName: .author.displayName authorEmail: .author.emailAddress relations: issue: .__issueKey ``` Sample payloadJSON ``` { "startAt": 0, "maxResults": 5000, "total": 1, "worklogs": [ { "self": "https://your-domain.atlassian.net/rest/api/3/issue/10001/worklog/10100", "author": { "self": "https://your-domain.atlassian.net/rest/api/3/user?accountId=712020%3Aabcdef12-3456-7890-abcd-ef1234567890", "accountId": "712020:abcdef12-3456-7890-abcd-ef1234567890", "emailAddress": "john.smith@example.com", "avatarUrls": { "48x48": "https://secure.gravatar.com/avatar/abc123?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-1.png", "24x24": "https://secure.gravatar.com/avatar/abc123?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-1.png", "16x16": "https://secure.gravatar.com/avatar/abc123?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-1.png", "32x32": "https://secure.gravatar.com/avatar/abc123?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-1.png" }, "displayName": "John Smith", "active": true, "timeZone": "America/New_York", "accountType": "atlassian" }, "updateAuthor": { "self": "https://your-domain.atlassian.net/rest/api/3/user?accountId=712020%3Aabcdef12-3456-7890-abcd-ef1234567890", "accountId": "712020:abcdef12-3456-7890-abcd-ef1234567890", "emailAddress": "john.smith@example.com", "avatarUrls": { "48x48": "https://secure.gravatar.com/avatar/abc123?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-1.png", "24x24": "https://secure.gravatar.com/avatar/abc123?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-1.png", "16x16": "https://secure.gravatar.com/avatar/abc123?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-1.png", "32x32": "https://secure.gravatar.com/avatar/abc123?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FJS-1.png" }, "displayName": "John Smith", "active": true, "timeZone": "America/New_York", "accountType": "atlassian" }, "comment": { "type": "doc", "version": 1, "content": [] }, "created": "2024-01-15T10:00:00.000+0000", "updated": "2024-01-15T10:00:00.000+0000", "started": "2024-01-15T09:00:00.000+0000", "timeSpent": "1d", "timeSpentSeconds": 28800, "id": "10100", "issueId": "10001" } ] } ``` Port entityJSON ``` { "identifier": "10100", "title": "John Smith - 2024-01-15T09:00:00.000+0000", "blueprint": "jiraWorklog", "properties": { "timeSpent": "1d", "timeSpentSeconds": 28800, "started": "2024-01-15T09:00:00.000+0000", "created": "2024-01-15T10:00:00.000+0000", "updated": "2024-01-15T10:00:00.000+0000", "authorAccountId": "712020:abcdef12-3456-7890-abcd-ef1234567890", "authorDisplayName": "John Smith", "authorEmail": "john.smith@example.com" }, "relations": { "issue": "TEST-1" } } ``` Default mappingYAML ``` - kind: board selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraBoard"' properties: type: .type isPrivate: .isPrivate projectKey: .location.projectKey // null projectTypeKey: .location.projectTypeKey // null adminUsers: '[.admins.users[]? | select(.accountId != null) | .accountId] | if length > 0 then . else null end' adminGroups: '[.admins.groups[]? | select(.name != null) | .name] | if length > 0 then . else null end' relations: project: '.__projectKeys // null' ``` Sample payloadJSON ``` { "id": 1, "self": "https://api.atlassian.com/ex/jira/33f08530-afd8-42fd-82cc-1dd5ebfeece8/rest/agile/1.0/board/1", "name": "PORT board", "type": "scrum", "location": { "projectId": 10000, "displayName": "Port (PORT)", "projectName": "Port", "projectKey": "PORT", "projectTypeKey": "software", "avatarURI": "https://api.atlassian.com/ex/jira/33f08530-afd8-42fd-82cc-1dd5ebfeece8/rest/api/2/universal_avatar/view/type/project/avatar/10560?size=small", "name": "Port (PORT)" }, "isPrivate": false, "__projectKeys": ["PORT"] } ``` Port entityJSON ``` { "identifier": "1", "title": "PORT board", "icon": "Jira", "blueprint": "jiraBoard", "team": [], "properties": { "type": "scrum", "isPrivate": false, "projectKey": "PORT", "projectTypeKey": "software", "adminUsers": null, "adminGroups": null }, "relations": { "project": ["PORT"] }, "createdAt": "2026-04-20T11:22:05.433Z", "createdBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW", "updatedAt": "2024-11-06T11:22:05.433Z", "updatedBy": "hBx3VFZjqgLPEoQLp7POx5XaoB0cgsxW" } ``` Default mappingYAML ``` - kind: sprint selector: query: "true" sprintState: - active port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraSprint"' properties: state: .state startDate: .startDate // null endDate: .endDate // null completeDate: .completeDate // null goal: .goal // null relations: board: .originBoardId | tostring ``` Sample payloadJSON ``` { "id": 353, "self": "https://api.atlassian.com/ex/jira/33f08530-afd8-42fd-82cc-1dd5ebfeece8/rest/agile/1.0/sprint/353", "state": "closed", "name": "June 2025", "startDate": "2025-06-01T08:33:48.592Z", "endDate": "2025-06-29T21:00:00.000Z", "completeDate": "2025-07-01T14:22:26.265Z", "createdDate": "2025-05-28T08:28:50.585Z", "originBoardId": 1, "goal": "To boldly sprint where no sprint has ran before" } ``` Port entityJSON ``` { "identifier": "353", "title": "June 2025", "blueprint": "jiraSprint", "properties": { "state": "closed", "startDate": "2025-06-01T08:33:48.592Z", "endDate": "2025-06-29T21:00:00.000Z", "completeDate": "2025-07-01T14:22:26.265Z", "goal": "To boldly sprint where no sprint has ran before" }, "relations": { "board": "1" } } ``` Default mappingYAML ``` - kind: epic selector: query: "true" status: ["incomplete"] port: entity: mappings: identifier: .id | tostring title: .name // .key blueprint: '"jiraEpic"' properties: name: .name // .key summary: .summary done: .done relations: board: .__boardId | tostring ``` Sample payloadJSON ``` { "id": 54339, "key": "PORT-10833", "self": "https://api.atlassian.com/ex/jira/33f08530-afd8-42fd-82cc-1dd5ebfeece8/rest/agile/1.0/epic/54339", "name": "Frontend Infra", "summary": "Frontend Infra", "color": { "key": "color_6" }, "issueColor": { "key": "green" }, "done": false } ``` Port entityJSON ``` { "identifier": "54339", "title": "Frontend Infra", "blueprint": "jiraEpic", "properties": { "name": "Frontend Infra", "summary": "Frontend Infra", "done": false }, "relations": { "board": "1" } } ``` Default mappingYAML ``` resources: - kind: component selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraComponent"' properties: description: .description assigneeType: .assigneeType issueCount: .issueCount ari: .componentBean.ari // null relations: project: .__project.key lead: .lead.accountId // null ``` Sample payloadJSON ``` { "isLast": false, "maxResults": 2, "nextPage": "https://your-domain.atlassian.net/rest/api/2/project/HSP/component?startAt=2&maxResults=2", "self": "https://your-domain.atlassian.net/rest/api/2/project/HSP/component?startAt=0&maxResults=2", "startAt": 0, "total": 7, "values": [ { "id": "10000", "name": "Component 1", "description": "This is a Jira component", "assigneeType": "PROJECT_LEAD", "isAssigneeTypeValid": false, "issueCount": 1, "project": "HSP", "projectId": 10000, "realAssigneeType": "PROJECT_LEAD", "self": "https://your-domain.atlassian.net/rest/api/2/component/10000", "lead": { "accountId": "5b10a2844c20165700ede21g", "accountType": "atlassian", "active": false, "displayName": "Mia Krystof", "self": "https://your-domain.atlassian.net/rest/api/2/user?accountId=5b10a2844c20165700ede21g" }, "assignee": { "accountId": "5b10a2844c20165700ede21g", "accountType": "atlassian", "active": false, "displayName": "Mia Krystof", "self": "https://your-domain.atlassian.net/rest/api/2/user?accountId=5b10a2844c20165700ede21g" }, "realAssignee": { "accountId": "5b10a2844c20165700ede21g", "accountType": "atlassian", "active": false, "displayName": "Mia Krystof", "self": "https://your-domain.atlassian.net/rest/api/2/user?accountId=5b10a2844c20165700ede21g" }, "componentBean": { "ari": "ari:cloud:compass:fdb3fdec-4e70-be56-11ee-0242ac120002:component/fdb3fdec-4e70-11ee-be56-0242ac120002/fdb3fdec-11ee-4e70-be56-0242ac120002", "id": "10000", "name": "Component 1", "description": "This is a Jira component", "project": "HSP", "projectId": 10000, "assigneeType": "PROJECT_LEAD", "isAssigneeTypeValid": false, "realAssigneeType": "PROJECT_LEAD", "self": "https://your-domain.atlassian.net/rest/api/2/component/10000" } } ] } ``` Port entityJSON ``` { "identifier": "10000", "title": "Component 1", "blueprint": "jiraComponent", "properties": { "description": "This is a Jira component", "assigneeType": "PROJECT_LEAD", "issueCount": 1, "ari": "ari:cloud:compass:fdb3fdec-4e70-be56-11ee-0242ac120002:component/fdb3fdec-4e70-11ee-be56-0242ac120002/fdb3fdec-11ee-4e70-be56-0242ac120002" }, "relations": { "project": "HSP", "lead": "5b10a2844c20165700ede21g" } } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Jira 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 Jira 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Jira is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Jira can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Limitations[​](#limitations "Direct link to Limitations") ### Getting user emails[​](#getting-user-emails "Direct link to Getting user emails") By default, Jira does not include user emails in API responses. To enable this: 1. Go to the [Jira admin panel](https://admin.atlassian.com/) and verify your domain under **Settings > Domains**. 2. After verification, claim your user accounts under the same **Domains** section. Once complete, Jira API responses will include the `emailAddress` field. See [Jira's documentation](https://support.atlassian.com/user-management/docs/verify-a-domain-to-manage-accounts/) for full details. ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the **jq playground** in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. Additional examples of blueprints and the relevant integration configurations can be found on the jira [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/examples/.md) ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=Jira). --- # Capabilities ### JQL support for issues[​](#jql-support-for-issues "Direct link to JQL support for issues") The `issue` kind selector supports two complementary filtering mechanisms: * **`jql`** ([Jira Query Language](https://support.atlassian.com/jira-service-management-cloud/docs/use-advanced-search-with-jira-query-language-jql/)) : Runs **server-side** on the Jira API. It controls which issues are fetched from Jira in the first place. Use this for broad filtering to reduce the volume of data returned by the API (e.g. excluding done issues, limiting to specific projects). * **`query`** (JQ expression) : Runs **client-side** on each fetched issue. It evaluates a [JQ boolean expression](https://stedolan.github.io/jq/manual/) against the raw issue data and skips any issue where the expression returns `false`. Use this for fine-grained filtering on fields available in the API response (e.g. filtering by a custom field value or sprint name). Both can be used together in the same selector. When combined, `jql` narrows the results from Jira, and `query` further filters them before ingestion into Port. ``` resources: - kind: issue # JQL filtering can only be used with the "issue" kind selector: query: "true" # JQ boolean expression. If evaluated to false - this object will be skipped. jql: "status != Done" # JQL query, will only ingest issues whose status is not "Done" port: ``` ### Fields support for the `issue` kind[​](#fields-support-for-the-issue-kind "Direct link to fields-support-for-the-issue-kind") The Jira integration allows you to customize what fields are available for ingestion using the `fields` selector. The `fields` selector accepts a comma-separated string containing what fields are available. Possible values are `*all`, `*navigate`, `` and `-` (specifically for excluding fields). The default value is set to `*all` which makes all fields available by default. More details what values the `field` selector allows is available on [Jira's issue API documentation](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-get:~:text=string-,fields,array%3Cstring%3E,-expand). ### Ingest sprint field for the `issue` kind[​](#ingest-sprint-field-for-the-issue-kind "Direct link to ingest-sprint-field-for-the-issue-kind") By default, the Jira integration does not include information on the issues' sprint. To ingest sprint information, a custom field must be added to issues to display sprints. This custom field is then included in the Jira issues mapping configuration. Follow the steps below: 1. Add Sprint as a custom field to Jira issue on your Jira dashboard. Take note of the custom field ID for the sprints field. This ID can be gotten by calling the [fields API](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-fields/#api-rest-api-3-field-get) to first retreive the list of fields for issues which returns a payload like so: **Issues field API response (click to expand)** ``` [ { "id": "thumbnail", "key": "thumbnail", "name": "Images", "custom": false, "orderable": false, "navigable": true, "searchable": false, "clauseNames": [] }, { "id": "created", "key": "created", "name": "Created", "custom": false, "orderable": false, "navigable": true, "searchable": true, "clauseNames": [ "created", "createdDate" ], "schema": { "type": "datetime", "system": "created" } }, { "id": "customfield_11111", "key": "customfield_11111", "name": "Sprint", "untranslatedName": "Sprint", "custom": true, "orderable": true, "navigable": true, "searchable": true, "clauseNames": [ "cf[11111]", "Sprint" ], "schema": { "type": "array", "items": "json", "custom": "com.pyxis.greenhopper.jira:gh-sprint", "customId": 11111 } } ] ``` Locate the `Sprint` field and take note of the ID - in this case, `customfield_11111`. 2. Extend the default blueprint to include a field for sprint: **Issue blueprint with `sprint` (click to expand)** Create in Port ``` { "identifier": "jiraIssue", "title": "Jira Issue", "icon": "Jira", "schema": { "properties": { "url": { "title": "Issue URL", "type": "string", "format": "url", "description": "URL to the issue in Jira" }, "status": { "title": "Status", "type": "string", "description": "The status of the issue" }, "issueType": { "title": "Type", "type": "string", "description": "The type of the issue" }, "components": { "title": "Components", "type": "array", "description": "The components related to this issue" }, "assignee": { "title": "Assignee", "type": "string", "format": "user", "description": "The user assigned to the issue" }, "reporter": { "title": "Reporter", "type": "string", "description": "The user that reported to the issue", "format": "user" }, "creator": { "title": "Creator", "type": "string", "description": "The user that created to the issue", "format": "user" }, "priority": { "title": "Priority", "type": "string", "description": "The priority of the issue" }, "sprint": { "title": "Sprint", "type": "string", "description": "The last sprint this issue belongs to" }, "created": { "title": "Created At", "type": "string", "description": "The created datetime of the issue", "format": "date-time" }, "updated": { "title": "Updated At", "type": "string", "description": "The updated datetime of the issue", "format": "date-time" } } }, "calculationProperties": { "handlingDuration": { "title": "Handling Duration (Days)", "icon": "Clock", "description": "The amount of time in days from issue creation to issue resolution", "calculation": "if (.properties.resolutionDate != null and .properties.created != null) then ((.properties.resolutionDate[0:19] + \"Z\" | fromdateiso8601) - (.properties.created[0:19] + \"Z\" | fromdateiso8601)) / 86400 else null end", "type": "number" } }, "mirrorProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "jiraProject", "title": "Project", "description": "The Jira project that contains this issue", "required": false, "many": false }, "parentIssue": { "target": "jiraIssue", "title": "Parent Issue", "required": false, "many": false }, "subtasks": { "target": "jiraIssue", "title": "Subtasks", "required": false, "many": true }, "assignee": { "target": "jiraUser", "title": "Assignee", "required": false, "many": false }, "reporter": { "target": "jiraUser", "title": "Reporter", "required": false, "many": false } } } ``` **Sprints field in issues payload response** On the issue response returned by calling the Jira issues API, sprints is returned as an array of sprints: **Sprints field in Jira API response (click to expand)** ``` { . . ., "customfield_11111": [ { "id": 37, "name": "Sprint 32", "state": "closed", "boardId": 1, "goal": "", "startDate": "2024-07-08T11:59:07.316Z", "endDate": "2024-07-28T21:00:00.000Z", "completeDate": "2024-07-29T14:04:34.397Z" }, { "id": 38, "name": "Sprint 33", "state": "closed", "boardId": 1, "goal": "", "startDate": "2024-07-29T14:05:25.295Z", "endDate": "2024-08-18T23:06:20.000Z", "completeDate": "2024-08-20T09:19:48.396Z" }, { "id": 40, "name": "Sprint 34", "state": "closed", "boardId": 1, "goal": "", "startDate": "2024-08-20T09:20:27.259Z", "endDate": "2024-09-08T20:30:00.000Z", "completeDate": "2024-09-10T13:50:01.397Z" }, { "id": 42, "name": "Sprint 35", "state": "active", "boardId": 1, "goal": "", "startDate": "2024-09-10T13:51:44.000Z", "endDate": "2024-10-15T01:01:00.000Z" } ], . . . } ``` For the purpose of this guide, we are simply retrieving the ID of the latest sprint. 3. Add the mapping configuration for the `sprint` field: **Issue with `sprint` field mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: issue selector: query: "true" jql: "statusCategory != Done" port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name components: .fields.components assignee: .fields.assignee.emailAddress reporter: .fields.reporter.emailAddress creator: .fields.creator.emailAddress priority: .fields.priority.name sprint: .fields.customfield_11111[-1].name // "" created: .fields.created updated: .fields.updated relations: project: .fields.project.key parentIssue: .fields.parent.key subtasks: .fields.subtasks | map(.key) ``` Where `customfield_11111` is your sprint field. 4. Click on `Resync` and watch sprints information being pulled alongside issues data. #### Link issues to sprint entities[​](#link-issues-to-sprint-entities "Direct link to Link issues to sprint entities") If you have enabled the `sprint` kind, you can link issues directly to their sprint entity in Port. This creates a navigable relation between `jiraIssue` and `jiraSprint`, enabling cross-blueprint views and aggregations. Extend the `jiraIssue` blueprint to add the sprint relation: ``` "relations": { "sprint": { "target": "jiraSprint", "title": "Sprint", "description": "The sprint this issue belongs to", "required": false, "many": false } } ``` Then add the relation mapping to your issue configuration, replacing `customfield_11111` with your sprint field ID: ``` relations: sprint: .fields.customfield_11111[-1].id | tostring ``` The sprint field returns an array of sprints an issue has belonged to. `.[-1]` retrieves the most recent sprint. The `.id` maps to the `jiraSprint` identifier which is `.id | tostring`. Sprint field ID The sprint field ID (`customfield_XXXXX`) is instance-specific. Follow step 1 in the section above to retrieve it from your Jira fields API. ### Link issues to their epic[​](#link-issues-to-their-epic "Direct link to Link issues to their epic") Jira issues carry a reference to their parent epic via the `epic` field in the issue API response. When the `epic` kind is enabled, you can wire this reference as a relation on `jiraIssue` pointing to `jiraEpic`, enabling cross-blueprint views — for example, listing all issues under a given epic directly from the epic entity page in Port. Extend the `jiraIssue` blueprint to add the epic relation: ``` "relations": { "epic": { "target": "jiraEpic", "title": "Epic", "description": "The epic this issue belongs to", "required": false, "many": false } } ``` Then add the relation mapping to your issue configuration: ``` relations: epic: (.fields.epic.id // null) | if . != null then tostring else null end ``` The `.fields.epic` field is populated by the Jira Software Cloud API for issues that belong to an epic. The mapping safely handles issues with no epic — `.fields.epic` is `null` for issues that do not belong to any epic, and the expression returns `null` in that case rather than failing. The `.id` is cast to a string to match the `jiraEpic` identifier format (`.id | tostring`). Epic field availability `.fields.epic` is returned by the Jira Software Cloud Agile REST API. It may not be present on all Jira project types. If your issues do not have this field populated, the relation will be `null` for those issues — no error occurs. ### Ingest issues based on the current sprint[​](#ingest-issues-based-on-the-current-sprint "Direct link to Ingest issues based on the current sprint") Ingesting only issues from the current sprint can be done by combining the `sprint` property with selector magic: **Issue from current sprint (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: issue selector: query: .fields.customfield_11111[-1].name == "Sprint 35" # Replace "Sprint 35" with the name of the current sprint jql: "statusCategory != Done" port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name components: .fields.components assignee: .fields.assignee.emailAddress reporter: .fields.reporter.emailAddress creator: .fields.creator.emailAddress priority: .fields.priority.name sprint: .fields.customfield_11111 | sort_by(.id) | .[-1].name // "" created: .fields.created updated: .fields.updated relations: project: .fields.project.key parentIssue: .fields.parent.key subtasks: .fields.subtasks | map(.key) ``` Issues with blank Sprint values If the `createMissingRelatedEntities` is set to `true`, issues with blank `Sprint` values and some empty/null properties will be created in Port. To avoid this, set `createMissingRelatedEntities` to `false`. ### Ingest worklogs per issue[​](#ingest-worklogs-per-issue "Direct link to Ingest worklogs per issue") The `worklog` kind fetches all time tracking entries logged against each issue and syncs them into Port as `jiraWorklog` entities. Each worklog is enriched with the key of the issue it belongs to, enabling the `issue` relation on the `jiraWorklog` blueprint. The selector's `jql` field controls which issues are scanned for worklogs. It defaults to `updated >= -1w` to avoid re-fetching the entire worklog history on every resync. ``` resources: - kind: worklog selector: query: 'true' jql: updated >= -1w port: entity: mappings: identifier: .id title: .author.displayName + " - " + .started blueprint: '"jiraWorklog"' properties: timeSpent: .timeSpent timeSpentSeconds: .timeSpentSeconds started: .started relations: issue: .__issueKey ``` Live events The `worklog` kind supports real-time updates. Port listens to `worklog_created`, `worklog_updated`, and `worklog_deleted` webhook events and updates entities immediately without waiting for the next resync. ### Link backlog issues to their board[​](#link-backlog-issues-to-their-board "Direct link to Link backlog issues to their board") The `backlog` kind fetches issues from each board's backlog and automatically enriches them with the board they belong to. This allows you to see which board an issue sits on directly from the `jiraIssue` entity in Port. To use this, add a `board` relation to your `jiraIssue` blueprint targeting `jiraBoard`, then include the `backlog` kind in your mapping: ``` resources: - kind: backlog selector: query: 'true' jql: statusCategory != Done port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name relations: project: .fields.project.key board: '.__boardId | tostring' ``` Kanban boards Boards that do not have a backlog enabled (such as Kanban boards with backlog disabled) are automatically skipped. This will not cause the resync to fail. ### Sync components per project[​](#sync-components-per-project "Direct link to Sync components per project") The `component` kind imports components from all Jira projects that are accessible to the integration and creates `jiraComponent` entities in Port. Each component is automatically associated with the Jira project it belongs to, making it easy to navigate between projects and their components in Port. Component leads are also linked to their corresponding jiraUser entities when available. Resync only Jira does not provide webhook events for component changes. As a result, component updates are discovered and synced during the next scheduled resync. ``` resources: - kind: component selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraComponent"' properties: description: .description assigneeType: .assigneeType issueCount: .issueCount ari: .componentBean.ari // null relations: project: .__project.key lead: .lead.accountId // null ``` --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ### Project[​](#project "Direct link to Project") **Project blueprint (click to expand)** Create in Port ``` { "identifier": "jiraProject", "title": "Jira Project", "icon": "Jira", "description": "A Jira project", "schema": { "properties": { "url": { "title": "Project URL", "type": "string", "format": "url", "description": "URL to the project in Jira" }, "totalIssues": { "title": "Total Issues", "type": "number", "description": "The total number of issues in the project" } } }, "mirrorProperties": {}, "calculationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** The `project` kind has a selector property, `expand` that specifies additional fields to be included in the response. It accepts a comma-separated string that allows you to include more fields in the response data that can be used in the mapping configuration. Possible values are `description`, `lead`, `issueTypes`, `url`, `projectKeys`, `insight`. If not specified, it defaults to `"insight"`. ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: "true" expand: "description,lead,issueTypes,url,projectKeys,insight" port: entity: mappings: identifier: .key title: .name blueprint: '"jiraProject"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key totalIssues: .insight.totalIssueCount ``` ### User[​](#user "Direct link to User") **User blueprint (click to expand)** Create in Port ``` { "identifier": "jiraUser", "title": "Jira User", "icon": "User", "description": "A Jira user account", "schema": { "properties": { "emailAddress": { "title": "Email", "type": "string", "format": "email", "description": "User's email address" }, "displayName": { "title": "Display Name", "type": "string", "description": "User's full name as displayed in Jira" }, "active": { "title": "Active Status", "type": "boolean", "description": "Whether the user account is active" }, "accountType": { "title": "Account Type", "type": "string", "description": "Type of Jira account (e.g., atlassian, customer)" } } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` - kind: user selector: query: "true" port: entity: mappings: identifier: .accountId title: .displayName blueprint: '"jiraUser"' properties: emailAddress: .emailAddress displayName: .displayName active: .active accountType: .accountType ``` ### Team[​](#team "Direct link to Team") **Team blueprint (click to expand)** Create in Port ``` { "identifier": "jiraTeam", "title": "Jira Team", "icon": "Users", "description": "A team within the organization", "schema": { "properties": { "organizationId": { "title": "Organization ID", "type": "string", "description": "Unique identifier for the parent organization" }, "teamType": { "title": "Team Type", "type": "string", "description": "Type of team (e.g., MEMBER_INVITE, OPEN)", "enum": [ "MEMBER_INVITE", "OPEN" ] }, "description": { "title": "Description", "type": "string", "description": "Team description" } } }, "relations": { "members": { "target": "jiraUser", "title": "Users", "description": "The Jira users belonging to this team", "required": false, "many": true } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {} } ``` **Integration configuration (click to expand)** ``` - kind: team selector: query: "true" includeMembers: true port: entity: mappings: identifier: .teamId title: .displayName blueprint: '"jiraTeam"' properties: organizationId: .organizationId teamType: .teamType description: .description relations: members: if .__members != null then .__members | map(.accountId) else [] end ``` ### Issue[​](#issue "Direct link to Issue") **Issue blueprint (click to expand)** Create in Port ``` { "identifier": "jiraIssue", "title": "Jira Issue", "icon": "Jira", "schema": { "properties": { "url": { "title": "Issue URL", "type": "string", "format": "url", "description": "URL to the issue in Jira" }, "status": { "title": "Status", "type": "string", "description": "The status of the issue" }, "issueType": { "title": "Type", "type": "string", "description": "The type of the issue" }, "components": { "title": "Components", "type": "array", "description": "The components related to this issue" }, "creator": { "title": "Creator", "type": "string", "description": "The user that created to the issue", "format": "user" }, "priority": { "title": "Priority", "type": "string", "description": "The priority of the issue" }, "labels": { "items": { "type": "string" }, "title": "Labels", "type": "array" }, "created": { "title": "Created At", "type": "string", "description": "The created datetime of the issue", "format": "date-time" }, "updated": { "title": "Updated At", "type": "string", "description": "The updated datetime of the issue", "format": "date-time" }, "resolutionDate": { "title": "Resolved At", "type": "string", "description": "The datetime the issue changed to a resolved state", "format": "date-time" } } }, "calculationProperties": { "handlingDuration": { "title": "Handling Duration (Days)", "icon": "Clock", "description": "The amount of time in days from issue creation to issue resolution", "calculation": "if (.properties.resolutionDate != null and .properties.created != null) then ((.properties.resolutionDate[0:19] + \"Z\" | fromdateiso8601) - (.properties.created[0:19] + \"Z\" | fromdateiso8601)) / 86400 else null end", "type": "number" } }, "mirrorProperties": {}, "aggregationProperties": {}, "relations": { "project": { "target": "jiraProject", "title": "Project", "description": "The Jira project that contains this issue", "required": false, "many": false }, "parentIssue": { "target": "jiraIssue", "title": "Parent Issue", "required": false, "many": false }, "subtasks": { "target": "jiraIssue", "title": "Subtasks", "required": false, "many": true }, "assignee": { "target": "jiraUser", "title": "Assignee", "required": false, "many": false }, "reporter": { "target": "jiraUser", "title": "Reporter", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` - kind: issue selector: query: "true" jql: "(statusCategory != Done) OR (created >= -1w) OR (updated >= -1w)" port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name components: .fields.components creator: .fields.creator.emailAddress priority: .fields.priority.name labels: .fields.labels created: .fields.created updated: .fields.updated resolutionDate: .fields.resolutiondate relations: project: .fields.project.key parentIssue: .fields.parent.key subtasks: .fields.subtasks | map(.key) assignee: .fields.assignee.accountId reporter: .fields.reporter.accountId ``` ### Release[​](#release "Direct link to Release") **Release blueprint (click to expand)** Create in Port ``` { "identifier": "jiraRelease", "title": "Jira Release", "icon": "Jira", "description": "A Jira project version (release)", "schema": { "properties": { "description": { "title": "Description", "type": "string", "description": "A description of the version" }, "released": { "title": "Released", "type": "boolean", "description": "Indicates whether the version has been released" }, "archived": { "title": "Archived", "type": "boolean", "description": "Indicates whether the version has been archived" }, "releaseDate": { "title": "Release Date", "type": "string", "format": "date", "description": "The release date of the version" }, "overdue": { "title": "Overdue", "type": "boolean", "description": "Indicates whether the version is overdue" } } }, "relations": { "project": { "target": "jiraProject", "title": "Project", "description": "The Jira project this version belongs to", "required": false, "many": false } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {} } ``` **Integration configuration (click to expand)** ``` - kind: release selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraRelease"' properties: description: .description released: .released archived: .archived releaseDate: .releaseDate overdue: .overdue relations: project: .__projectKey ``` ### Worklog[​](#worklog "Direct link to Worklog") The `worklog` kind ingests time tracking entries logged against Jira issues. Each worklog is enriched with the key of its parent issue via `__issueKey`, enabling a direct relation to `jiraIssue` in Port. Live events (`worklog_created`, `worklog_updated`, `worklog_deleted`) are supported. **Worklog blueprint (click to expand)** Create in Port ``` { "identifier": "jiraWorklog", "title": "Jira Worklog", "icon": "Jira", "schema": { "properties": { "timeSpent": { "title": "Time Spent", "type": "string" }, "timeSpentSeconds": { "title": "Time Spent (Seconds)", "type": "number" }, "started": { "title": "Started", "type": "string", "format": "date-time" }, "created": { "title": "Created", "type": "string", "format": "date-time" }, "updated": { "title": "Updated", "type": "string", "format": "date-time" }, "authorAccountId": { "title": "Author", "type": "string" }, "authorDisplayName": { "title": "Author Name", "type": "string" }, "authorEmail": { "title": "Author Email", "type": "string", "format": "email" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "issue": { "target": "jiraIssue", "title": "Issue", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: worklog selector: query: 'true' jql: updated >= -1w port: entity: mappings: identifier: .id title: .author.displayName + " - " + .started blueprint: '"jiraWorklog"' properties: timeSpent: .timeSpent timeSpentSeconds: .timeSpentSeconds started: .started created: .created updated: .updated authorAccountId: .author.accountId authorDisplayName: .author.displayName authorEmail: .author.emailAddress relations: issue: .__issueKey ``` ### Component[​](#component "Direct link to Component") The `component` kind ingests project components from Jira. Each component is enriched with its parent project key via `__project.key` for the `project` relation, and exposes `issueCount`, `assigneeType`, and `lead` directly from the Jira API. The `ari` property is populated for Compass-linked components and is `null` for standard Jira components. **Component blueprint (click to expand)** Create in Port ``` { "identifier": "jiraComponent", "title": "Jira Component", "icon": "Jira", "schema": { "properties": { "description": { "title": "Description", "type": "string" }, "assigneeType": { "title": "Assignee Type", "type": "string", "description": "How new issues assigned to this component are routed.", "enum": [ "PROJECT_DEFAULT", "COMPONENT_LEAD", "PROJECT_LEAD", "UNASSIGNED" ], "enumColors": { "PROJECT_DEFAULT": "blue", "COMPONENT_LEAD": "green", "PROJECT_LEAD": "purple", "UNASSIGNED": "lightGray" } }, "issueCount": { "title": "Issue Count", "type": "number" }, "ari": { "title": "ARI", "type": "string", "description": "Atlassian Resource Identifier. Present only for Compass-linked components." } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Project", "target": "jiraProject", "required": false, "many": false }, "lead": { "title": "Lead", "target": "jiraUser", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: component selector: query: 'true' port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraComponent"' properties: description: .description assigneeType: .assigneeType issueCount: .issueCount ari: .componentBean.ari // null relations: project: .__project.key lead: .lead.accountId // null ``` #### Component mapping configuration options[​](#component-mapping-configuration-options "Direct link to Component mapping configuration options") * Component source * Name filter Controls which components are returned. Defaults to `jira` to return only standard project components. Set to `compass` to return only Compass global components linked to the project, or `auto` to return both. ``` - kind: component selector: query: 'true' componentSource: compass ``` Filter components by name or description prefix. Useful for large projects where only a subset of components is relevant. ``` - kind: component selector: query: 'true' nameFilter: backend ``` #### Worklog mapping configuration options[​](#worklog-mapping-configuration-options "Direct link to Worklog mapping configuration options") The `worklog` kind exposes five optional selector fields. * JQL filter * Started after * Started before * Expand Scopes which issues are scanned for worklogs. Defaults to issues updated in the last week to avoid re-fetching the full worklog history on every resync. Set to an empty string to fetch worklogs for all issues. ``` - kind: worklog selector: query: 'true' jql: "project = PORT AND updated >= -1w" ``` Only return worklogs where work started after this time. Accepts a UNIX timestamp in milliseconds. Passed via `apiQueryParams`. ``` - kind: worklog selector: query: 'true' apiQueryParams: startedAfter: 1700000000000 ``` Only return worklogs where work started before this time. Accepts a UNIX timestamp in milliseconds. Passed via `apiQueryParams`. ``` - kind: worklog selector: query: 'true' apiQueryParams: startedBefore: 1800000000000 ``` Include additional worklog information in the response. Accepts a comma-separated list; `properties`, returns custom worklog properties. Enabling this increases payload size and may slow large syncs. Passed via `apiQueryParams`. ``` - kind: worklog selector: query: 'true' apiQueryParams: expand: properties ``` Performance On large Jira instances, combine a tight `jql` filter with `startedAfter` to minimise the number of issues scanned and worklogs fetched per resync. ### Backlog[​](#backlog "Direct link to Backlog") **Backlog blueprint (click to expand)** Create in Port ``` { "identifier": "jiraIssue", "title": "Jira Issue", "icon": "Jira", "schema": { "properties": { "url": { "title": "Issue URL", "type": "string", "format": "url" }, "status": { "title": "Status", "type": "string" }, "issueType": { "title": "Type", "type": "string" }, "priority": { "title": "Priority", "type": "string" }, "labels": { "title": "Labels", "type": "array", "items": { "type": "string" } }, "components": { "title": "Components", "type": "array", "items": { "type": "string" } }, "creator": { "title": "Creator", "type": "string" }, "created": { "title": "Created At", "type": "string", "format": "date-time" }, "updated": { "title": "Updated At", "type": "string", "format": "date-time" }, "resolutionDate": { "title": "Resolution Date", "type": "string", "format": "date-time" }, "storyPoints": { "title": "Story Points", "type": "number", "description": "The story point estimate for this issue" }, "team": { "title": "Team", "type": "string", "description": "The team this issue is assigned to" }, "sprint": { "title": "Sprint", "type": "string", "description": "The most recent sprint this issue belongs to" }, "sprintHistory": { "title": "Sprint History", "type": "array", "items": { "type": "string" }, "description": "All sprints this issue has been part of, oldest to newest. An issue appearing in multiple sprints indicates carry-over." }, "dueDate": { "title": "Due Date", "type": "string", "description": "The due date of the issue" } } }, "relations": { "project": { "target": "jiraProject", "title": "Project", "required": false, "many": false }, "parentIssue": { "target": "jiraIssue", "title": "Parent Issue", "required": false, "many": false }, "board": { "target": "jiraBoard", "title": "Board", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: backlog selector: query: 'true' jql: statusCategory != Done port: entity: mappings: identifier: .key title: .fields.summary blueprint: '"jiraIssue"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/browse/" + .key status: .fields.status.name issueType: .fields.issuetype.name priority: .fields.priority.name labels: .fields.labels components: '[.fields.components[]?.name]' created: .fields.created updated: .fields.updated resolutionDate: .fields.resolutiondate creator: .fields.creator.accountId dueDate: .fields.duedate storyPoints: .fields.customfield_10026 // null team: .fields.customfield_10001.name // null sprint: .fields.customfield_10020 | if . != null and length > 0 then .[-1].name else null end sprintHistory: '[.fields.customfield_10020[]?.name] | if length > 0 then . else null end' relations: project: .fields.project.key parentIssue: (.fields.parent // null) | if . != null then .key else null end board: '.__boardId | tostring' jira_user_assignee: .fields.assignee.accountId // null jira_user_reporter: .fields.reporter.accountId // null ``` #### Backlog mapping configuration options[​](#backlog-mapping-configuration-options "Direct link to Backlog mapping configuration options") The `backlog` kind exposes four optional selector fields. * JQL filter * Fields * Use software API Filters which issues are returned from each board's backlog. Defaults to recently updated or incomplete issues to avoid re-ingesting the full backlog history on every resync. Set to an empty string to fetch all backlog issues. ``` - kind: backlog selector: query: "true" jql: "statusCategory != Done" # Only fetch incomplete issues ``` Restricts which issue fields are returned. When omitted, all fields are returned. Use this to reduce payload size on large boards. ``` - kind: backlog selector: query: "true" fields: - id - key - summary - status ``` Controls which Jira endpoint is used to fetch backlog issues. Defaults to `true`, which uses the `rest/software/1.0` backlog endpoint. Set to `false` to force the legacy `rest/agile/1.0` endpoint. ``` - kind: backlog selector: query: "true" useSoftwareApi: false # Force legacy agile/1.0 endpoint ``` agile/1.0 deprecation Atlassian deprecated all `rest/agile/1.0` issue listing endpoints on May 1, 2026, with removal scheduled for **November 1, 2026**. This includes the board backlog, sprint, epic, and board issue endpoints. Keep `useSoftwareApi` set to `true` (the default) so the integration uses the replacement `rest/software/1.0` endpoints. See [Atlassian's deprecation notice](https://developer.atlassian.com/cloud/jira/software/changelog/) for the full list of affected endpoints and their replacements. ### Board[​](#board "Direct link to Board") **Board blueprint (click to expand)** Create in Port ``` { "identifier": "jiraBoard", "title": "Jira Board", "icon": "Jira", "schema": { "properties": { "type": { "title": "Board Type", "type": "string", "description": "The type of the board — scrum, kanban, or simple", "enum": ["scrum", "kanban", "simple"] }, "isPrivate": { "title": "Private", "type": "boolean", "description": "Whether the board is private" }, "projectKey": { "title": "Project Key", "type": "string", "description": "The key of the primary project this board belongs to" }, "projectTypeKey": { "title": "Project Type", "type": "string", "description": "The type of the project (e.g. software, business)" }, "adminUsers": { "title": "Admin Users", "type": "array", "items": { "type": "string" }, "description": "Account IDs of users with admin access to this board" }, "adminGroups": { "title": "Admin Groups", "type": "array", "items": { "type": "string" }, "description": "Names of groups with admin access to this board" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "project": { "title": "Projects", "description": "The Jira projects associated with this board", "target": "jiraProject", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` - kind: project selector: query: "true" port: entity: mappings: identifier: .key title: .name blueprint: '"jiraProject"' properties: url: (.self | split("/") | .[:3] | join("/")) + "/projects/" + .key totalIssues: .insight.totalIssueCount - kind: board selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraBoard"' properties: type: .type isPrivate: .isPrivate projectKey: .location.projectKey // null projectTypeKey: .location.projectTypeKey // null adminUsers: '[.admins.users[]? | select(.accountId != null) | .accountId] | if length > 0 then . else null end' adminGroups: '[.admins.groups[]? | select(.name != null) | .name] | if length > 0 then . else null end' relations: project: '.__projectKeys // null' ``` #### Boards mapping configuration options[​](#boards-mapping-configuration-options "Direct link to Boards mapping configuration options") The `board` kind exposes two optional selector fields that control which boards are fetched from Jira. * Filter board by type * Filter board by project Filters boards by type. Accepted values are `scrum`, `kanban`, and `simple`. When omitted, all board types are fetched. ``` - kind: board selector: query: "true" boardType: scrum # Only fetch scrum boards ``` Filters boards scoped to a specific Jira project. Accepts a project key (e.g. `PORT`) or a numeric project ID. When omitted, boards from all projects are fetched. ``` - kind: board selector: query: "true" projectKey: PORT # Only fetch boards scoped to the PORT project ``` Performance On large Jira instances with hundreds of boards, using `boardType` and `projectKey` together significantly reduces the number of boards fetched and the number of subsequent API calls made to enrich each board with its associated projects. If you only care about a specific project's boards, always set `projectKey`. ### Sprint[​](#sprint "Direct link to Sprint") **Sprint blueprint (click to expand)** Create in Port ``` { "identifier": "jiraSprint", "title": "Jira Sprint", "icon": "Jira", "schema": { "properties": { "state": { "title": "State", "type": "string", "description": "The current state of the sprint", "enum": ["active", "closed", "future"] }, "startDate": { "title": "Start Date", "type": "string", "format": "date-time", "description": "The date the sprint started" }, "endDate": { "title": "End Date", "type": "string", "format": "date-time", "description": "The date the sprint is scheduled to end" }, "completeDate": { "title": "Complete Date", "type": "string", "format": "date-time", "description": "The date the sprint was completed. Null if not yet closed." }, "goal": { "title": "Goal", "type": "string", "description": "The sprint goal" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "board": { "title": "Origin Board", "description": "The board this sprint was created on", "target": "jiraBoard", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` - kind: board selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraBoard"' properties: type: .type isPrivate: .isPrivate projectKey: .location.projectKey // null projectTypeKey: .location.projectTypeKey // null relations: project: '.__projectKeys // null' - kind: sprint selector: query: "true" sprintState: - active port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraSprint"' properties: state: .state startDate: .startDate // null endDate: .endDate // null completeDate: .completeDate // null goal: .goal // null relations: board: .originBoardId | tostring ``` #### Filtering sprints with selectors[​](#filtering-sprints-with-selectors "Direct link to Filtering sprints with selectors") The `sprint` kind exposes an optional `sprintState` selector that controls which sprints are fetched per board. Accepted values are `active`, `closed`, and `future`, and multiple states can be provided as a list. When set to a single state, only sprints in that state are fetched from each board. ``` - kind: sprint selector: query: "true" sprintState: - active # Only fetch active sprints (default) ``` ``` - kind: sprint selector: query: "true" sprintState: - active - future # Fetch active and future sprints ``` ``` - kind: sprint selector: query: "true" sprintState: - active - closed - future # Fetch all sprint states ``` Fetching all sprint states You can also omit `sprintState` entirely or set it to `null` to fetch all sprints regardless of state: ``` - kind: sprint selector: query: "true" sprintState: null # Fetch all sprints — active, closed, and future ``` `sprintState` defaults to `["active"]` to protect large Jira instances from excessive API calls during resync. On mature Jira instances, closed sprints accumulate over years and can number in the hundreds per board. Fetching all states across hundreds of boards multiplies resync time significantly. Opt in to closed or future sprints explicitly when you need them. ### Epic[​](#epic "Direct link to Epic") **Epic blueprint (click to expand)** Create in Port ``` { "identifier": "jiraEpic", "title": "Jira Epic", "icon": "Jira", "schema": { "properties": { "name": { "title": "Name", "type": "string", "description": "The epic name. Falls back to the Jira issue key (e.g. PORT-6459) when the name is not set." }, "summary": { "title": "Summary", "type": "string", "description": "The epic summary" }, "done": { "title": "Done", "type": "boolean", "description": "Whether the epic is completed" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "board": { "title": "Board", "description": "The Jira board this epic appears on", "target": "jiraBoard", "required": false, "many": true } } } ``` **Integration configuration (click to expand)** ``` - kind: board selector: query: "true" port: entity: mappings: identifier: .id | tostring title: .name blueprint: '"jiraBoard"' properties: type: .type isPrivate: .isPrivate projectKey: .location.projectKey // null projectTypeKey: .location.projectTypeKey // null relations: project: '.__projectKeys // null' - kind: epic selector: query: "true" done: "false" port: entity: mappings: identifier: .id | tostring title: .name // .key blueprint: '"jiraEpic"' properties: name: .name // .key summary: .summary done: .done relations: board: .__boardId | tostring ``` Title fallback If an epic's `name` field is empty, the integration uses the issue key (for example, `PORT-6459`) as the value for both `title` and `name`. #### Filtering epics by status[​](#filtering-epics-by-status "Direct link to Filtering epics by status") The `epic` kind exposes an optional `status` selector that controls which epics are fetched per board. Accepts `"incomplete"`, `"complete"`, or both. *Fetch only incomplete epics (default):* ``` - kind: epic selector: query: "true" status: ["incomplete"] ``` *Fetch only completed epics:* ``` - kind: epic selector: query: "true" status: ["complete"] ``` *Fetch all epics regardless of status:* ``` - kind: epic selector: query: "true" status: ["incomplete", "complete"] ``` Performance `status` defaults to `["incomplete"]` to protect large Jira instances from fetching their full epic history on first install. Completed epics accumulate over years and can number in the hundreds per board. Opt in to completed epics explicitly when you need them. ### Slack notifications[​](#slack-notifications "Direct link to Slack notifications") Using Port's automation capabilities, you can set up real-time Slack notifications when issues are created or updated in Jira. **Automation for new bug reports (click to expand)** Create in Port ``` { "identifier": "jira_new_bug_notification", "title": "Notify Slack on New Jira Bug", "icon": "Slack", "description": "Sends a Slack notification when a new bug is created in Jira", "trigger": { "type": "automation", "event": { "type": "ENTITY_CREATED", "blueprintIdentifier": "jiraIssue" }, "condition": { "type": "JQ", "expressions": [ ".diff.after.properties.issueType == \"Bug\"" ], "combinator": "and" } }, "invocationMethod": { "type": "WEBHOOK", "url": "YOUR_SLACK_WEBHOOK_URL", "agent": false, "synchronized": true, "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "text": "🐛 New Bug Reported\n\n*Issue:* {{ .event.diff.after.title }}\n*Project:* {{ .event.diff.after.relations.project }}\n*Status:* {{ .event.diff.after.properties.status }}\n*Priority:* {{ .event.diff.after.properties.priority }}\n\n<{{ .event.diff.after.properties.url }}|View in Jira> | " } }, "publish": true } ``` **Automation for high-priority tasks (click to expand)** Create in Port ``` { "identifier": "jira_high_priority_task", "title": "Notify Slack on High-Priority Jira Task", "icon": "Slack", "description": "Sends a Slack notification when a high-priority task is created or updated in Jira", "trigger": { "type": "automation", "event": { "type": "ENTITY_UPDATED", "blueprintIdentifier": "jiraIssue" }, "condition": { "type": "JQ", "expressions": [ ".diff.after.properties.priority == \"Highest\" or .diff.after.properties.priority == \"High\"", ".diff.before.properties.priority != \"Highest\" and .diff.before.properties.priority != \"High\"" ], "combinator": "and" } }, "invocationMethod": { "type": "WEBHOOK", "url": "YOUR_SLACK_WEBHOOK_URL", "agent": false, "synchronized": true, "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "text": "⚠️ High-Priority Issue Alert\n\n*Issue:* {{ .event.diff.after.title }}\n*Type:* {{ .event.diff.after.properties.issueType }}\n*Priority:* {{ .event.diff.after.properties.priority }}\n*Status:* {{ .event.diff.after.properties.status }}\n\n<{{ .event.diff.after.properties.url }}|View in Jira> | " } }, "publish": true } ``` Slack webhook URL Replace `YOUR_SLACK_WEBHOOK_URL` with your actual Slack incoming webhook URL. For information on creating Slack webhooks, see the [Slack API documentation](https://api.slack.com/messaging/webhooks). These automations allow your team to receive immediate notifications in Slack when bugs are reported or issues are updated with high priority, making your project management workflow more efficient. --- # Linear ![Linear icon](/img/guides/icons/Linear.svg)![Linear icon](/img/guides/icons/dark/Linear.svg) Loading version... Port's Linear integration allows you to model Linear resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Team](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/TeamConnection)[Issue](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/IssueConnection)[Label](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/IssueLabelConnection)[Document](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/Document)[User](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/UserConnection)[Project](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/ProjectConnection)[Initiative](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/InitiativeConnection)[Team member](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/TeamMembershipConnection)[Workflow state](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/WorkflowStateConnection)[Cycle](https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/objects/CycleConnection) ## Core support [Live eventsSee supported resources](#set-up-live-events) ## Setup[​](#setup "Direct link to Setup") Choose one of the following installation methods. Not sure which method is right for your use case? Check the available [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port (Recommended) * ✧Port hosts and manages the integration * ✧Real-time updates + customizable resync interval * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration you get after installing the Linear integration. **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: team selector: query: 'true' port: entity: mappings: identifier: .key title: .name blueprint: '"linearTeam"' properties: description: .description workspaceName: .organization.name url: '"https://linear.app/" + .organization.urlKey + "/team/" + .key' relations: members: "[.members.nodes[].id]" parentTeam: .parent.key subTeams: "[.children[].key]" - kind: label selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"linearLabel"' properties: isGroup: .isGroup relations: parentLabel: .parent.id childLabels: '[.children.edges[].node.id]' - kind: issue selector: query: 'true' port: entity: mappings: identifier: .identifier title: .title blueprint: '"linearIssue"' properties: url: .url status: .state.name assignee: .assignee.email creator: .creator.email priority: .priorityLabel created: .createdAt updated: .updatedAt relations: team: .team.key labels: .labelIds parentIssue: .parent.identifier - kind: document selector: query: 'true' port: entity: mappings: identifier: .id title: .title blueprint: '"linearDocument"' properties: url: .url summary: .summary content: .content slugId: .slugId parentType: 'if .project then "project" elif .issue then "issue" elif .team then "team" elif .initiative then "initiative" elif .cycle then "cycle" elif .release then "release" else null end' project: .project.name initiative: .initiative.name cycle: .cycle.number creator: .creator.email owner: .owner.email created: .createdAt updated: .updatedAt relations: team: .team.key issue: .issue.identifier - kind: user selector: query: 'true' port: entity: mappings: identifier: .id title: .displayName // .name blueprint: '"linearUser"' properties: name: .name email: .email url: .url title: .title active: .active admin: .admin guest: .guest created: .createdAt updated: .updatedAt relations: teams: "[.teamMemberships.nodes[].team.key]" - kind: project selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"linearProject"' properties: url: .url description: .description status: .status.name health: .health progress: .progress priority: .priorityLabel startDate: .startDate targetDate: .targetDate lead: .lead.email created: .createdAt updated: .updatedAt relations: teams: "[.teams.nodes[].key]" leadUser: .lead.id - kind: initiative selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"linearInitiative"' properties: url: .url description: .description status: .status health: .health priority: .priority targetDate: if .targetDate then (.targetDate | if test("T") then . else . + "T00:00:00Z" end) else null end startedAt: .startedAt completedAt: .completedAt slugId: .slugId creator: .creator.email owner: .owner.email created: .createdAt updated: .updatedAt relations: leadTeam: .leadTeam.key ownerUser: .owner.id parentInitiative: .parentInitiative.id subInitiatives: "[.subInitiatives.nodes[].id]" projects: "[.projects.nodes[].id]" - kind: workflow-state selector: query: 'true' port: entity: mappings: identifier: .id title: .name blueprint: '"linearWorkflowState"' properties: type: .type color: .color position: .position description: .description created: .createdAt updated: .updatedAt relations: team: .team.id - kind: cycle selector: query: 'true' port: entity: mappings: identifier: .id title: .name // "Cycle " + (.number | tostring) blueprint: '"linearCycle"' properties: number: .number description: .description progress: .progress startsAt: .startsAt endsAt: .endsAt completedAt: .completedAt isActive: .isActive isFuture: .isFuture isPast: .isPast created: .createdAt updated: .updatedAt relations: team: .team.key ``` ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to 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 [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. TeamLabelIssueDocumentUserProjectInitiativeTeam memberWorkflow stateCycle Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: team selector: query: "true" port: entity: mappings: identifier: .key title: .name blueprint: '"linearTeam"' properties: description: .description workspaceName: .organization.name url: '"https://linear.app/" + .organization.urlKey + "/team/" + .key' relations: members: "[.members.nodes[].id]" parentTeam: .parent.key subTeams: "[.children[].key]" ``` Sample payloadJSON ``` { "id": "92d25fa4-fb1c-449f-b314-47f82e8f280d", "name": "Port", "key": "POR", "description": null, "organization": { "id": "36968e1b-496c-4610-8c25-641364da172e", "name": "Getport", "urlKey": "getport" } } ``` Port entityJSON ``` { "identifier": "POR", "title": "Port", "icon": "Linear", "blueprint": "linearTeam", "team": [], "properties": { "url": "https://linear.app/getport/team/POR", "workspaceName": "Getport" }, "relations": {}, "createdAt": "2024-05-19T16:19:15.232Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:15.232Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: label selector: query: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"linearLabel"' properties: isGroup: .isGroup relations: parentLabel: .parent.id childLabels: "[.children.edges[].node.id]" ``` Sample payloadJSON ``` { "id": "36f84d2c-7b7d-4a71-96f2-6ea4140004d5", "createdAt": "2024-05-17T15:17:40.858Z", "updatedAt": "2024-05-17T15:17:40.858Z", "archivedAt": null, "name": "New-sample-label", "description": null, "color": "#bec2c8", "isGroup": true, "parent": null, "children": { "edges": [ { "node": { "id": "2e483c90-2aca-4db6-924d-b0571d49f691" } } ] } } ``` Port entityJSON ``` { "identifier": "36f84d2c-7b7d-4a71-96f2-6ea4140004d5", "title": "New-sample-label", "icon": "Linear", "blueprint": "linearLabel", "team": [], "properties": { "isGroup": false }, "relations": { "childLabels": [], "parentLabel": null }, "createdAt": "2024-05-19T16:19:17.747Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:17.747Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: issue selector: query: "true" port: entity: mappings: identifier: .identifier title: .title blueprint: '"linearIssue"' properties: url: .url status: .state.name assignee: .assignee.email creator: .creator.email priority: .priorityLabel created: .createdAt updated: .updatedAt relations: team: .team.key labels: .labelIds parentIssue: .parent.identifier ``` Sample payloadJSON ``` { "id": "9b4745c2-a8e6-4432-9e56-0fa97b79ccbf", "createdAt": "2024-05-16T21:52:00.299Z", "updatedAt": "2024-05-17T09:27:40.077Z", "archivedAt": null, "number": 2, "title": "sub issue with new title", "priority": 3, "estimate": null, "sortOrder": -991, "startedAt": null, "completedAt": null, "startedTriageAt": null, "triagedAt": null, "canceledAt": null, "autoClosedAt": null, "autoArchivedAt": null, "dueDate": null, "slaStartedAt": null, "slaBreachesAt": null, "trashed": null, "snoozedUntilAt": null, "labelIds": [ "402b218c-938c-4ddf-85db-0019bc632316" ], "previousIdentifiers": [], "subIssueSortOrder": -56.17340471045278, "priorityLabel": "Medium", "integrationSourceType": null, "identifier": "POR-2", "url": "https://linear.app/getport/issue/POR-2/sub-issue-with-new-title", "branchName": "alex/por-2-sub-issue-with-new-title", "customerTicketCount": 0, "description": "", "descriptionState": "AQG/pOWPAgAHAQtwcm9zZW1pcnJvcgMJcGFyYWdyYXBoAA==", "team": { "id": "92d25fa4-fb1c-449f-b314-47f82e8f280d", "name": "Port", "key": "POR" }, "state": { "name": "Todo" }, "creator": { "name": "Alex Rivera", "email": "alex.rivera@example.com" }, "assignee": { "name": "Dudi Elhadad", "email": "dudi@getport.io" }, "parent": { "id": "5ddd8e85-ad89-4c96-b901-0b901b29100d", "identifier": "POR-1" } } ``` Port entityJSON ``` { "identifier": "POR-2", "title": "sub issue with new title", "icon": "Linear", "blueprint": "linearIssue", "team": [], "properties": { "status": "Todo", "url": "https://linear.app/getport/issue/POR-2/sub-issue-with-new-title", "created": "2024-05-16T21:52:00.299Z", "priority": "Medium", "assignee": "dudi@getport.io", "updated": "2024-05-17T09:27:40.077Z", "creator": "alex.rivera@example.com" }, "relations": { "team": "POR", "labels": [ "402b218c-938c-4ddf-85db-0019bc632316" ], "parentIssue": "POR-1" }, "createdAt": "2024-05-19T16:19:21.143Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:21.143Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: document selector: query: "true" port: entity: mappings: identifier: .id title: .title blueprint: '"linearDocument"' properties: url: .url summary: .summary content: .content slugId: .slugId parentType: 'if .project then "project" elif .issue then "issue" elif .team then "team" elif .initiative then "initiative" elif .cycle then "cycle" elif .release then "release" else null end' project: .project.name initiative: .initiative.name cycle: .cycle.number creator: .creator.email owner: .owner.email created: .createdAt updated: .updatedAt relations: team: .team.key issue: .issue.identifier ``` Sample payloadJSON ``` { "id": "50e3e770-03ef-4c12-9f5a-e3122a768bc4", "title": "payment-service-prd", "summary": "", "icon": null, "color": null, "slugId": "66165efe01cd", "url": "https://linear.app/domain/document/payment-service-prd-66165efe01cd", "content": "Here goes the actual content\n\n1. Intro\n2. Body\n3. Conclusion", "documentContentId": "c8d7bbd8-218d-472f-a831-ab14b0d06cda", "createdAt": "2026-08-18T14:41:42.691Z", "updatedAt": "2026-08-18T14:42:25.011Z", "archivedAt": null, "hiddenAt": null, "trashed": null, "sortOrder": 83.28, "creator": { "id": "2791218c-1df6-4dcc-9eaf-2c2becd9ed05", "name": "Port Developer", "email": "developer@getport.io" }, "owner": { "id": "2791218c-1df6-4dcc-9eaf-2c2becd9ed05", "name": "Port Developer", "email": "developer@getport.io" }, "updatedBy": { "id": "2791218c-1df6-4dcc-9eaf-2c2becd9ed05", "name": "Port Developer", "email": "developer@getport.io" }, "lastAppliedTemplate": null, "project": { "id": "800b565e-1885-4c0b-af43-d0226e55f7bb", "name": "Surveys" }, "initiative": null, "team": null, "issue": null, "release": null, "cycle": null } ``` Port entityJSON ``` { "identifier": "50e3e770-03ef-4c12-9f5a-e3122a768bc4", "title": "payment-service-prd", "icon": "Linear", "blueprint": "linearDocument", "properties": { "url": "https://linear.app/getport/document/payment-service-prd-66165efe01cd", "summary": "", "content": "Here goes the actual content\n\n1. Intro\n2. Body\n3. Conclusion", "slugId": "66165efe01cd", "parentType": "project", "project": "Surveys", "creator": "developer@getport.io", "owner": "developer@getport.io", "created": "2026-08-18T14:41:42.691Z", "updated": "2026-08-18T14:42:25.011Z" }, "relations": {} } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: user selector: query: "true" port: entity: mappings: identifier: .id title: .displayName // .name blueprint: '"linearUser"' properties: name: .name email: .email url: .url title: .title active: .active admin: .admin guest: .guest created: .createdAt updated: .updatedAt relations: teams: "[.teamMemberships.nodes[].team.key]" ``` Sample payloadJSON ``` { "id": "11c5ce7d-229b-4487-b23b-f404e4a8c85d", "name": "Alex Rivera", "displayName": "Alex Rivera", "email": "alex.rivera@example.com", "active": true, "admin": false, "guest": false, "url": "https://linear.app/getport/profiles/alex", "title": "Engineering Manager", "createdAt": "2024-01-15T10:00:00.000Z", "updatedAt": "2024-05-19T16:19:15.232Z", "teamMemberships": { "nodes": [ { "team": { "key": "POR" } } ] } } ``` Port entityJSON ``` { "identifier": "11c5ce7d-229b-4487-b23b-f404e4a8c85d", "title": "Alex Rivera", "icon": "Linear", "blueprint": "linearUser", "team": [], "properties": { "name": "Alex Rivera", "email": "alex.rivera@example.com", "url": "https://linear.app/getport/profiles/alex", "title": "Engineering Manager", "active": true, "admin": false, "guest": false, "created": "2024-01-15T10:00:00.000Z", "updated": "2024-05-19T16:19:15.232Z" }, "relations": { "teams": [ "POR" ] }, "createdAt": "2024-05-19T16:19:15.232Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:15.232Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: project selector: query: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"linearProject"' properties: url: .url description: .description status: .status.name health: .health progress: .progress priority: .priorityLabel startDate: .startDate targetDate: .targetDate lead: .lead.email created: .createdAt updated: .updatedAt relations: teams: "[.teams.nodes[].key]" leadUser: .lead.id ``` Sample payloadJSON ``` { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Payments platform", "description": "Modernize payment processing infrastructure", "url": "https://linear.app/getport/project/payments-platform-abc123", "progress": 0.45, "health": "onTrack", "priority": 2, "priorityLabel": "High", "startDate": "2024-03-01T00:00:00.000Z", "targetDate": "2024-09-30T00:00:00.000Z", "createdAt": "2024-02-15T10:00:00.000Z", "updatedAt": "2024-05-19T16:19:15.232Z", "status": { "name": "In Progress", "type": "started" }, "lead": { "id": "11c5ce7d-229b-4487-b23b-f404e4a8c85d", "name": "Alex Rivera", "email": "alex.rivera@example.com" }, "teams": { "nodes": [ { "id": "92d25fa4-fb1c-449f-b314-47f82e8f280d", "key": "POR", "name": "Port" } ] } } ``` Port entityJSON ``` { "identifier": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "title": "Payments platform", "icon": "Linear", "blueprint": "linearProject", "team": [], "properties": { "url": "https://linear.app/getport/project/payments-platform-abc123", "description": "Modernize payment processing infrastructure", "status": "In Progress", "health": "onTrack", "progress": 0.45, "priority": "High", "startDate": "2024-03-01T00:00:00.000Z", "targetDate": "2024-09-30T00:00:00.000Z", "lead": "alex.rivera@example.com", "created": "2024-02-15T10:00:00.000Z", "updated": "2024-05-19T16:19:15.232Z" }, "relations": { "teams": [ "POR" ], "leadUser": "11c5ce7d-229b-4487-b23b-f404e4a8c85d" }, "createdAt": "2024-05-19T16:19:15.232Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:15.232Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: initiative selector: query: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"linearInitiative"' properties: url: .url description: .description status: .status health: .health priority: .priority targetDate: if .targetDate then (.targetDate | if test("T") then . else . + "T00:00:00Z" end) else null end startedAt: .startedAt completedAt: .completedAt slugId: .slugId creator: .creator.email owner: .owner.email created: .createdAt updated: .updatedAt relations: leadTeam: .leadTeam.key ownerUser: .owner.id parentInitiative: .parentInitiative.id subInitiatives: "[.subInitiatives.nodes[].id]" projects: "[.projects.nodes[].id]" ``` Sample payloadJSON ``` { "id": "b7e8f9a0-1234-5678-9abc-def012345678", "name": "Platform modernization", "description": "Modernize core platform infrastructure across all teams", "url": "https://linear.app/getport/initiative/platform-modernization-abc123", "slugId": "platform-modernization-abc123", "status": "Active", "health": "onTrack", "priority": 2, "targetDate": "2024-09-30", "startedAt": "2024-03-01T00:00:00.000Z", "completedAt": null, "createdAt": "2024-02-15T10:00:00.000Z", "updatedAt": "2024-05-19T16:19:15.232Z", "creator": { "id": "11c5ce7d-229b-4487-b23b-f404e4a8c85d", "name": "Alex Rivera", "email": "alex.rivera@example.com" }, "owner": { "id": "11c5ce7d-229b-4487-b23b-f404e4a8c85d", "name": "Alex Rivera", "email": "alex.rivera@example.com" }, "leadTeam": { "id": "92d25fa4-fb1c-449f-b314-47f82e8f280d", "key": "POR", "name": "Port" }, "parentInitiative": null, "subInitiatives": { "nodes": [] }, "projects": { "nodes": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" } ] } } ``` Port entityJSON ``` { "identifier": "b7e8f9a0-1234-5678-9abc-def012345678", "title": "Platform modernization", "icon": "Linear", "blueprint": "linearInitiative", "team": [], "properties": { "url": "https://linear.app/getport/initiative/platform-modernization-abc123", "description": "Modernize core platform infrastructure across all teams", "status": "Active", "health": "onTrack", "priority": 2, "targetDate": "2024-09-30T00:00:00Z", "startedAt": "2024-03-01T00:00:00.000Z", "completedAt": null, "slugId": "platform-modernization-abc123", "creator": "alex.rivera@example.com", "owner": "alex.rivera@example.com", "created": "2024-02-15T10:00:00.000Z", "updated": "2024-05-19T16:19:15.232Z" }, "relations": { "leadTeam": "POR", "ownerUser": "11c5ce7d-229b-4487-b23b-f404e4a8c85d", "projects": [ "a1b2c3d4-e5f6-7890-abcd-ef1234567890" ] }, "createdAt": "2024-05-19T16:19:15.232Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:15.232Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: team-members selector: query: "true" port: entity: mappings: identifier: .id title: .user.displayName // .user.name blueprint: '"linearTeamMember"' properties: email: .user.email isOwner: .owner created: .createdAt updated: .updatedAt relations: team: .team.key user: .user.id ``` Sample payloadJSON ``` { "id": "c3d4e5f6-a7b8-9012-cdef-345678901234", "owner": false, "createdAt": "2024-01-15T10:00:00.000Z", "updatedAt": "2024-05-19T16:19:15.232Z", "team": { "id": "92d25fa4-fb1c-449f-b314-47f82e8f280d", "key": "POR", "name": "Port" }, "user": { "id": "11c5ce7d-229b-4487-b23b-f404e4a8c85d", "name": "Alex Rivera", "email": "alex.rivera@example.com", "displayName": "Alex Rivera" } } ``` Port entityJSON ``` { "identifier": "c3d4e5f6-a7b8-9012-cdef-345678901234", "title": "Alex Rivera", "icon": "Linear", "blueprint": "linearTeamMember", "team": [], "properties": { "email": "alex.rivera@example.com", "isOwner": false, "created": "2024-01-15T10:00:00.000Z", "updated": "2024-05-19T16:19:15.232Z" }, "relations": { "team": "POR", "user": "11c5ce7d-229b-4487-b23b-f404e4a8c85d" }, "createdAt": "2024-05-19T16:19:15.232Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:15.232Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: workflow-state selector: query: "true" port: entity: mappings: identifier: .id title: .name blueprint: '"linearWorkflowState"' properties: type: .type color: .color position: .position description: .description created: .createdAt updated: .updatedAt relations: team: .team.id ``` Sample payloadJSON ``` { "id": "f12cad17-9b8f-470d-b20a-5e17da8e46b9", "name": "In Progress", "type": "started", "color": "#f2c94c", "position": 2, "description": null, "createdAt": "2024-01-15T10:00:00.000Z", "updatedAt": "2024-05-19T16:19:15.232Z", "team": { "id": "92d25fa4-fb1c-449f-b314-47f82e8f280d", "key": "POR", "name": "Port" } } ``` Port entityJSON ``` { "identifier": "f12cad17-9b8f-470d-b20a-5e17da8e46b9", "title": "In Progress", "icon": "Linear", "blueprint": "linearWorkflowState", "team": [], "properties": { "type": "started", "color": "#f2c94c", "position": 2, "description": null, "created": "2024-01-15T10:00:00.000Z", "updated": "2024-05-19T16:19:15.232Z" }, "relations": { "team": "92d25fa4-fb1c-449f-b314-47f82e8f280d" }, "createdAt": "2024-05-19T16:19:15.232Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:15.232Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` Default mappingYAML ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: cycle selector: query: "true" port: entity: mappings: identifier: .id title: .name // "Cycle " + (.number | tostring) blueprint: '"linearCycle"' properties: number: .number description: .description progress: .progress startsAt: .startsAt endsAt: .endsAt completedAt: .completedAt isActive: .isActive isFuture: .isFuture isPast: .isPast created: .createdAt updated: .updatedAt relations: team: .team.key ``` Sample payloadJSON ``` { "id": "f8e7d6c5-b4a3-2109-8765-4321fedcba09", "number": 12, "name": "Cycle 12", "description": "Q2 planning cycle", "startsAt": "2024-05-01T00:00:00.000Z", "endsAt": "2024-05-14T00:00:00.000Z", "completedAt": null, "progress": 0.3, "isActive": true, "isFuture": false, "isPast": false, "createdAt": "2024-04-28T10:00:00.000Z", "updatedAt": "2024-05-19T16:19:15.232Z", "team": { "id": "92d25fa4-fb1c-449f-b314-47f82e8f280d", "key": "POR", "name": "Port" } } ``` Port entityJSON ``` { "identifier": "f8e7d6c5-b4a3-2109-8765-4321fedcba09", "title": "Cycle 12", "icon": "Linear", "blueprint": "linearCycle", "team": [], "properties": { "number": 12, "description": "Q2 planning cycle", "progress": 0.3, "startsAt": "2024-05-01T00:00:00.000Z", "endsAt": "2024-05-14T00:00:00.000Z", "completedAt": null, "isActive": true, "isFuture": false, "isPast": false, "created": "2024-04-28T10:00:00.000Z", "updated": "2024-05-19T16:19:15.232Z" }, "relations": { "team": "POR" }, "createdAt": "2024-05-19T16:19:15.232Z", "createdBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE", "updatedAt": "2024-05-19T16:19:15.232Z", "updatedBy": "KZ5zDPudPshQMShUb4cLopBEE1fNSJGE" } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * 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-reference/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/' \ -H 'Authorization: Bearer ' ``` Selecting a Port API URL by account region 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://app.port.io)) → `https://api.port.io` * **US** ([app.us.port.io](https://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:///live-events/ ``` 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[​](#baseurl--webhook-configuration "Direct link to 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 Linear 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 Linear 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Linear is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Linear can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) --- # Terraform Cloud & Terraform Enterprise ![Terraform icon](/img/guides/icons/Terraform.svg)![Terraform icon](/img/guides/icons/Terraform.svg) Loading version... Port's Terraform Cloud integration allows you to model Terraform Cloud resources in your software catalog and ingest data into them. Supported resources ![External link icon](/img/icons/external-link.svg)[Organization](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/organizations)[Project](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/projects)[Workspace](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces)[Run](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/run)[State Version](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/state-versions)[State File](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/state-versions#fetch-the-current-state-version-for-a-workspace)[Health Assessment](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/assessment-results) ## Core support [Live eventsSee supported resources](#set-up-live-events) Terraform enterprise (self hosted) Port supports both Terraform Cloud and Terraform Enterprise versions (self hosted). The following data model and use cases are common for both integrations. If installing Port exporter for Terraform Enterprise, you will be required to specify your Terraform 's host URL by passing the following parameter to the installer: `integration.config.terraformCloudHost` ## Setup[​](#setup "Direct link to Setup") Choose your preferred installation method below. Not sure which to pick? See the [installation methods overview](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). 1Hosted by Port2Install Hosted by Port * ✧Port hosts and manages the integration * ✧Customizable resync interval for data ingestion * ✧Access control flexibility Self-hosted * ✧Host and manage the integration in your infrastructure * ✧Real-time updates via webhooks * ✧Best for high-security or custom networking needs CI * ✧Run as a one-time job in your CI pipeline or on a scheduled basis * ✧Minimal infrastructure management Install ## Configuration[​](#configuration "Direct link to Configuration") Port integrations use a [YAML mapping block](/context-lake/ingestion/configure-mapping/configuration-methods/.md#configuration-structure) to ingest data from the third-party api into Port. The mapping makes use of the [JQ JSON processor](https://stedolan.github.io/jq/manual/) to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API. ### Default mapping configuration[​](#default-mapping-configuration "Direct link to Default mapping configuration") This is the default mapping configuration for this integration: **Default mapping configuration (click to expand)** ``` createMissingRelatedEntities: true deleteDependentEntities: true resources: - kind: organization selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudOrganization"' properties: externalId: .attributes."external-id" ownerEmail: .attributes.email collaboratorAuthPolicy: .attributes."collaborator-auth-policy" planExpired: .attributes."plan-expired" planExpiresAt: .attributes."plan-expires-at" permissions: .attributes.permissions samlEnabled: .attributes."saml-enabled" defaultExecutionMode: .attributes."default-execution-mode" - kind: project selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudProject"' properties: name: .attributes.name permissions: .attributes.permissions relations: organization: .relationships.organization.data.id - kind: workspace selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudWorkspace"' properties: organization: .relationships.organization.data.id createdAt: .attributes."created-at" updatedAt: .attributes."updated-at" terraformVersion: .attributes."terraform-version" locked: .attributes.locked executionMode: .attributes."execution-mode" resourceCount: .attributes."resource-count" latestChangeAt: .attributes."latest-change-at" tags: .__tags relations: currentStateVersion: .relationships."current-state-version".data.id project: .relationships.project.data.id - kind: state-version selector: query: 'true' port: entity: mappings: identifier: .id title: .id blueprint: '"terraformCloudStateVersion"' properties: createdAt: .attributes."created-at" serial: .attributes.serial status: .attributes.status size: .attributes.size isResourcesProcessed: .attributes."resources-processed" hostedStateDownloadUrl: .attributes."hosted-state-download-url" hostedJsonDownloadUrl: .attributes."hosted-json-state-download-url" vcsCommitUrl: .attributes."vcs-commit-url" outputData: .__output - kind: run selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.message blueprint: '"terraformCloudRun"' properties: createdAt: .attributes."created-at" status: .attributes.status hasChanges: .attributes."has-changes" isDestroy: .attributes."is-destroy" message: .attributes.message terraformVersion: .attributes."terraform-version" appliedAt: .attributes."status-timestamps"."applied-at" plannedAt: .attributes."status-timestamps"."planned-at" source: .attributes.source relations: terraformCloudWorkspace: .relationships.workspace.data.id - kind: state-file selector: query: 'true' port: entity: mappings: identifier: .lineage + "-" + (.serial | tostring) title: '"State v" + (.serial | tostring)' blueprint: '"terraformCloudStateFile"' properties: version: .version terraformVersion: .terraform_version serial: .serial lineage: .lineage resourceCount: .resources | length resourceTypes: .resources | map(.type) | unique | sort providers: .resources | map(.provider) | unique | sort outputKeys: .outputs | keys ``` ## Mapping & selectors per resource[​](#mapping--selectors-per-resource "Direct link to Mapping & selectors per resource") Use the explorer below to view sample payloads and the resulting Port entities for each resource type. For additional resources and advanced configurations, see the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/terraform-cloud/examples/.md). OrganizationProjectWorkspaceRunState VersionState FileHealth Assessment Default mappingYAML ``` - kind: organization selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudOrganization"' properties: externalId: .attributes."external-id" ownerEmail: .attributes.email collaboratorAuthPolicy: .attributes."collaborator-auth-policy" planExpired: .attributes."plan-expired" planExpiresAt: .attributes."plan-expires-at" permissions: .attributes.permissions samlEnabled: .attributes."saml-enabled" defaultExecutionMode: .attributes."default-execution-mode" ``` Sample payloadJSON ``` { "id":"example-org-484f07", "type":"organizations", "attributes":{ "external-id":"org-S8Wo1fyUtpSFHbGQ", "created-at":"2023-12-19T14:16:20.491Z", "email":"example@getport.io", "session-timeout":"None", "session-remember":"None", "collaborator-auth-policy":"password", "plan-expired":false, "plan-expires-at":"2024-01-18T14:16:20.637Z", "plan-is-trial":true, "plan-is-enterprise":false, "plan-identifier":"free_standard", "cost-estimation-enabled":false, "managed-resource-count":5, "send-passing-statuses-for-untriggered-speculative-plans":false, "allow-force-delete-workspaces":false, "assessments-enforced":false, "is-in-degraded-mode":false, "default-execution-mode":"remote", "remaining-testable-count":5, "aggregated-commit-status-enabled":false, "name":"example-org-484f07", "permissions":{ "can-update":true, "can-update-authentication":true, "can-destroy":true, "can-access-via-teams":true, "can-create-module":true, "can-create-team":false, "can-create-workspace":true, "can-manage-users":true, "can-manage-subscription":true, "can-manage-sso":true, "can-update-oauth":true, "can-update-sentinel":true, "can-update-ssh-keys":true, "can-update-api-token":true, "can-traverse":true, "can-view-usage":true, "can-start-trial":false, "can-update-agent-pools":true, "can-manage-tags":true, "can-manage-varsets":true, "can-read-varsets":true, "can-manage-public-providers":true, "can-create-provider":true, "can-manage-public-modules":true, "can-manage-custom-providers":true, "can-manage-run-tasks":true, "can-read-run-tasks":true, "can-create-project":true, "can-manage-assessments":true, "can-read-assessments":true, "can-view-explorer":true, "can-deploy-no-code-modules":false, "can-manage-no-code-modules":false, "can-use-new-pnp-activation-ui":true }, "saml-enabled":false, "fair-run-queuing-enabled":false, "owners-team-saml-role-id":"None", "two-factor-conformant":true }, "relationships":{ "default-agent-pool":{ "data":"None" }, "meta":{ "links":{ "related":"/api/v2/organizations/example-org-484f07/meta" } }, "oauth-tokens":{ "links":{ "related":"/api/v2/organizations/example-org-484f07/oauth-tokens" } }, "authentication-token":{ "links":{ "related":"/api/v2/organizations/example-org-484f07/authentication-token" } }, "entitlement-set":{ "data":{ "id":"org-S8Wo1fyUtpSFHbGQ", "type":"entitlement-sets" }, "links":{ "related":"/api/v2/organizations/example-org-484f07/entitlement-set" } }, "subscription":{ "data":{ "id":"sub-THrhah4DkbD4etzy", "type":"subscriptions" }, "links":{ "related":"/api/v2/organizations/example-org-484f07/subscription" } }, "default-project":{ "data":{ "id":"prj-d1JbKcLJBhwN66Vs", "type":"projects" }, "links":{ "related":"/api/v2/projects/prj-d1JbKcLJBhwN66Vs" } } }, "links":{ "self":"/api/v2/organizations/example-org-484f07" } } ``` Port entityJSON ``` { "identifier": "example-org-484f07", "title": "example-org-484f07", "team": [], "properties": { "externalId": "org-S8Wo1fyUtpSFHbGQ", "collaboratorAuthPolicy": "password", "permissions": { "can-update": true, "can-update-authentication": true, "can-destroy": true, "can-access-via-teams": true, "can-create-module": true, "can-create-team": false, "can-create-workspace": true, "can-manage-users": true, "can-manage-subscription": true, "can-manage-sso": true, "can-update-oauth": true, "can-update-sentinel": true, "can-update-ssh-keys": true, "can-update-api-token": true, "can-traverse": true, "can-view-usage": true, "can-start-trial": false, "can-update-agent-pools": true, "can-manage-tags": true, "can-manage-varsets": true, "can-read-varsets": true, "can-manage-public-providers": true, "can-create-provider": true, "can-manage-public-modules": true, "can-manage-custom-providers": true, "can-manage-run-tasks": true, "can-read-run-tasks": true, "can-create-project": true, "can-manage-assessments": true, "can-read-assessments": true, "can-view-explorer": true, "can-deploy-no-code-modules": false, "can-manage-no-code-modules": false, "can-use-new-pnp-activation-ui": true }, "samlEnabled": false, "defaultExecutionMode": "remote", "ownerEmail": "example@getport.io", "planExpired": "false", "planExpiresAt": "2024-01-18T14:16:20.637Z" }, "relations": {}, "icon": "Terraform" } ``` Default mappingYAML ``` - kind: project selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudProject"' properties: name: .attributes.name permissions: .attributes.permissions relations: organization: .relationships.organization.data.id ``` Port entityJSON ``` { "identifier": "prj-wnLLjhXa3XArrRFR", "title": "Default Project", "team": [], "properties": { "name": "Default Project", "permissions": { "can-read": true, "can-update": true, "can-destroy": true, "can-create-workspace": true, "can-move-workspace": true, "can-deploy-no-code-modules": true, "can-read-teams": true, "can-manage-teams": true }, "organizationId": "example-org-162af6" }, "relations": {}, "icon": "Terraform" } ``` Default mappingYAML ``` - kind: workspace selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudWorkspace"' properties: organization: .relationships.organization.data.id createdAt: .attributes."created-at" updatedAt: .attributes."updated-at" terraformVersion: .attributes."terraform-version" locked: .attributes.locked executionMode: .attributes."execution-mode" resourceCount: .attributes."resource-count" latestChangeAt: .attributes."latest-change-at" tags: .__tags relations: currentStateVersion: .relationships."current-state-version".data.id project: .relationships.project.data.id ``` Sample payloadJSON ``` { "id":"ws-DBJebej1fNCZomkr", "type":"workspaces", "attributes":{ "allow-destroy-plan":true, "auto-apply":false, "auto-apply-run-trigger":false, "auto-destroy-activity-duration":"None", "auto-destroy-at":"None", "auto-destroy-status":"None", "created-at":"2023-12-12T12:43:36.192Z", "environment":"default", "locked":false, "name":"example-workspace-2", "queue-all-runs":false, "speculative-enabled":true, "structured-run-output-enabled":true, "terraform-version":"1.6.5", "working-directory":"", "global-remote-state":false, "updated-at":"2023-12-12T12:43:36.192Z", "resource-count":0, "apply-duration-average":"None", "plan-duration-average":"None", "policy-check-failures":"None", "run-failures":"None", "workspace-kpis-runs-count":"None", "latest-change-at":"2023-12-12T12:43:36.192Z", "operations":true, "execution-mode":"remote", "vcs-repo":"None", "vcs-repo-identifier":"None", "permissions":{ "can-update":true, "can-destroy":true, "can-queue-run":true, "can-read-variable":true, "can-update-variable":true, "can-read-state-versions":true, "can-read-state-outputs":true, "can-create-state-versions":true, "can-queue-apply":true, "can-lock":true, "can-unlock":true, "can-force-unlock":true, "can-read-settings":true, "can-manage-tags":true, "can-manage-run-tasks":true, "can-force-delete":true, "can-manage-assessments":true, "can-manage-ephemeral-workspaces":false, "can-read-assessment-results":true, "can-queue-destroy":true }, "actions":{ "is-destroyable":true }, "description":"None", "file-triggers-enabled":false, "trigger-prefixes":[ ], "trigger-patterns":[ ], "assessments-enabled":false, "last-assessment-result-at":"None", "source":"tfe-ui", "source-name":"None", "source-url":"None", "tag-names":[ "foo", "bar" ], "setting-overwrites":{ "execution-mode":false, "agent-pool":false } }, "relationships":{ "organization":{ "data":{ "id":"example-org-162af6", "type":"organizations" } }, "current-run":{ "data":"None" }, "latest-run":{ "data":"None" }, "outputs":{ "data":[ ] }, "remote-state-consumers":{ "links":{ "related":"/api/v2/workspaces/ws-DBJebej1fNCZomkr/relationships/remote-state-consumers" } }, "current-state-version":{ "data":"None" }, "current-configuration-version":{ "data":"None" }, "agent-pool":{ "data":"None" }, "readme":{ "data":"None" }, "project":{ "data":{ "id":"prj-wnLLjhXa3XArrRFR", "type":"projects" } }, "current-assessment-result":{ "data":"None" }, "vars":{ "data":[ ] } }, "links":{ "self":"/api/v2/organizations/example-org-162af6/workspaces/example-workspace-2", "self-html":"/app/example-org-162af6/workspaces/example-workspace-2" }, "__tags":[ { "id":"tag-moR1pPNpT2vowy55", "type":"tags", "attributes":{ "name":"foo", "created-at":"2024-01-09T19:41:45.183Z", "instance-count":1 }, "relationships":{ "organization":{ "data":{ "id":"example-org-162af6", "type":"organizations" } } } }, { "id":"tag-PNyYYGibnxZcnVho", "type":"tags", "attributes":{ "name":"bar", "created-at":"2024-01-09T19:41:45.197Z", "instance-count":1 }, "relationships":{ "organization":{ "data":{ "id":"example-org-162af6", "type":"organizations" } } } } ] } ``` Port entityJSON ``` { "identifier": "ws-DBJebej1fNCZomkr", "title": "example-workspace-2", "team": [], "properties": { "createdAt": "2023-12-12T12:43:36.192Z", "updatedAt": "2023-12-12T12:43:36.192Z", "terraformVersion": "1.6.5", "locked": false, "executionMode": "remote", "resourceCount": 0, "latestChangeAt": "2023-12-12T12:43:36.192Z", "organization": "example-org-162af6", "tags": [ { "id": "tag-moR1pPNpT2vowy55", "type": "tags", "attributes": { "name": "foo", "created-at": "2024-01-09T19:41:45.183Z", "instance-count": 1 }, "relationships": { "organization": { "data": { "id": "example-org-162af6", "type": "organizations" } } } }, { "id": "tag-PNyYYGibnxZcnVho", "type": "tags", "attributes": { "name": "bar", "created-at": "2024-01-09T19:41:45.197Z", "instance-count": 1 }, "relationships": { "organization": { "data": { "id": "example-org-162af6", "type": "organizations" } } } } ] }, "relations": { "project": "prj-wnLLjhXa3XArrRFR" }, "icon": "Terraform" } ``` Default mappingYAML ``` - kind: run selector: query: 'true' port: entity: mappings: identifier: .id title: .attributes.message blueprint: '"terraformCloudRun"' properties: createdAt: .attributes."created-at" status: .attributes.status hasChanges: .attributes."has-changes" isDestroy: .attributes."is-destroy" message: .attributes.message terraformVersion: .attributes."terraform-version" appliedAt: .attributes."status-timestamps"."applied-at" plannedAt: .attributes."status-timestamps"."planned-at" source: .attributes.source relations: terraformCloudWorkspace: .relationships.workspace.data.id ``` Sample payloadJSON ``` { "data": [ { "id": "run-SFSeL9fg6Kibje8L", "type": "runs", "attributes": { "actions": { "is-cancelable": false, "is-confirmable": false, "is-discardable": false, "is-force-cancelable": false }, "allow-config-generation": true, "allow-empty-apply": false, "auto-apply": false, "canceled-at": "None", "created-at": "2023-12-13T12:12:40.252Z", "has-changes": false, "is-destroy": false, "message": "just checking this out", "plan-only": false, "refresh": true, "refresh-only": false, "replace-addrs": [], "save-plan": false, "source": "tfe-ui", "status-timestamps": { "planned-at": "2023-12-13T12:12:54+00:00", "queuing-at": "2023-12-13T12:12:40+00:00", "planning-at": "2023-12-13T12:12:49+00:00", "plan-queued-at": "2023-12-13T12:12:40+00:00", "plan-queueable-at": "2023-12-13T12:12:40+00:00", "planned-and-finished-at": "2023-12-13T12:12:54+00:00" }, "status": "planned_and_finished", "target-addrs": "None", "trigger-reason": "manual", "terraform-version": "1.6.5", "permissions": { "can-apply": true, "can-cancel": true, "can-comment": true, "can-discard": true, "can-force-execute": true, "can-force-cancel": true, "can-override-policy-check": true }, "variables": [] }, "relationships": { "workspace": { "data": { "id": "ws-WWhD18B59v5ndTTP", "type": "workspaces" } }, "apply": { "data": { "id": "apply-ToVWRgBe4mmGwTf7", "type": "applies" }, "links": { "related": "/api/v2/runs/run-SFSeL9fg6Kibje8L/apply" } }, "configuration-version": { "data": { "id": "cv-ompZmuF15X68njap", "type": "configuration-versions" }, "links": { "related": "/api/v2/runs/run-SFSeL9fg6Kibje8L/configuration-version" } }, "created-by": { "data": { "id": "user-Vg6uYxyhrQSHNrKU", "type": "users" }, "links": { "related": "/api/v2/runs/run-SFSeL9fg6Kibje8L/created-by" } }, "plan": { "data": { "id": "plan-3rXS4BMT8TEkdchh", "type": "plans" }, "links": { "related": "/api/v2/runs/run-SFSeL9fg6Kibje8L/plan" } }, "run-events": { "data": [ { "id": "re-WgvYmckRJjafwU5R", "type": "run-events" }, { "id": "re-46PfZixftNeifEG9", "type": "run-events" }, { "id": "re-LCCwB2pQNPrGnveF", "type": "run-events" }, { "id": "re-YoviSEov4cscqfi7", "type": "run-events" } ], "links": { "related": "/api/v2/runs/run-SFSeL9fg6Kibje8L/run-events" } }, "task-stages": { "data": [], "links": { "related": "/api/v2/runs/run-SFSeL9fg6Kibje8L/task-stages" } }, "policy-checks": { "data": [], "links": { "related": "/api/v2/runs/run-SFSeL9fg6Kibje8L/policy-checks" } }, "comments": { "data": [], "links": { "related": "/api/v2/runs/run-SFSeL9fg6Kibje8L/comments" } } }, "links": { "self": "/api/v2/runs/run-SFSeL9fg6Kibje8L" } } ] } ``` Port entityJSON ``` { "identifier": "run-SFSeL9fg6Kibje8L", "title": "just checking this out", "blueprint": "terraformRun", "properties": { "runId": "run-SFSeL9fg6Kibje8L", "createdAt": "2021-08-16T21:50:58.726Z", "status": "planned_and_finished", "hasChanges": false, "isDestroy": false, "message": "just checking this out", "terraformVersion": "0.11.1", "appliedAt": null, "plannedAt": "2023-12-13T12:12:54+00:00", "source": "tfe-api" }, "relations": {} } ``` Default mappingYAML ``` - kind: state-version selector: query: 'true' port: entity: mappings: identifier: .id title: .id blueprint: '"terraformCloudStateVersion"' properties: createdAt: .attributes."created-at" serial: .attributes.serial status: .attributes.status size: .attributes.size isResourcesProcessed: .attributes."resources-processed" hostedStateDownloadUrl: .attributes."hosted-state-download-url" hostedJsonDownloadUrl: .attributes."hosted-json-state-download-url" vcsCommitUrl: .attributes."vcs-commit-url" outputData: .__output ``` Sample payloadJSON ``` { "id": "sv-wZEoyjPg1KYsjYZg", "type": "state-versions", "attributes": { "created-at": "2023-12-20T07:08:14.113Z", "size": 8554, "hosted-state-download-url": "https://app.terraform.io/api/state-versions/sv-wZEoyjPg1KYsjYZg/hosted_state", "hosted-json-state-download-url": "https://app.terraform.io/api/state-versions/sv-wZEoyjPg1KYsjYZg/hosted_json_state", "modules": { "root": { "random_string": 1, "aws_db_instance": 1, "aws_db_subnet_group": 1, "data.aws_availability_zones": 1 }, "root.vpc": { "aws_eip": 2, "aws_vpc": 1, "aws_route": 3, "aws_subnet": 4, "aws_nat_gateway": 2, "aws_route_table": 3, "aws_internet_gateway": 1, "aws_route_table_association": 4 }, "root.elb-http.elb": { "aws_elb": 1 }, "root.ec2-instances": { "aws_instance": 4, "data.aws_ami": 1 }, "root.lb-security-group.sg": { "aws_security_group": 1, "aws_security_group_rule": 6 }, "root.app-security-group.sg": { "aws_security_group": 1, "aws_security_group_rule": 6 }, "root.elb-http.elb-attachment": { "aws_elb_attachment": 4 } }, "providers": { "provider[\"registry.terraform.io/hashicorp/aws\"]": { "aws_eip": 2, "aws_elb": 1, "aws_vpc": 1, "aws_route": 3, "aws_subnet": 4, "aws_instance": 4, "data.aws_ami": 1, "aws_db_instance": 1, "aws_nat_gateway": 2, "aws_route_table": 3, "aws_elb_attachment": 4, "aws_security_group": 2, "aws_db_subnet_group": 1, "aws_internet_gateway": 1, "aws_security_group_rule": 12, "aws_route_table_association": 4, "data.aws_availability_zones": 1 }, "provider[\"registry.terraform.io/hashicorp/random\"]": { "random_string": 1 } }, "resources": [ { "name": "available", "type": "data.aws_availability_zones", "count": 1, "module": "root", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "database", "type": "aws_db_instance", "count": 1, "module": "root", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "private", "type": "aws_db_subnet_group", "count": 1, "module": "root", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "lb_id", "type": "random_string", "count": 1, "module": "root", "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", "index-keys": [] }, { "name": "this_name_prefix", "type": "aws_security_group", "count": 1, "module": "root.app_security_group.sg", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "egress_rules", "type": "aws_security_group_rule", "count": 1, "module": "root.app_security_group.sg", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "ingress_rules", "type": "aws_security_group_rule", "count": 4, "module": "root.app_security_group.sg", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "ingress_with_self", "type": "aws_security_group_rule", "count": 1, "module": "root.app_security_group.sg", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "amazon_linux", "type": "data.aws_ami", "count": 1, "module": "root.ec2_instances", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "app", "type": "aws_instance", "count": 4, "module": "root.ec2_instances", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "this", "type": "aws_elb", "count": 1, "module": "root.elb_http.elb", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "this", "type": "aws_elb_attachment", "count": 4, "module": "root.elb_http.elb_attachment", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "this_name_prefix", "type": "aws_security_group", "count": 1, "module": "root.lb_security_group.sg", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "egress_rules", "type": "aws_security_group_rule", "count": 1, "module": "root.lb_security_group.sg", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "ingress_rules", "type": "aws_security_group_rule", "count": 4, "module": "root.lb_security_group.sg", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "ingress_with_self", "type": "aws_security_group_rule", "count": 1, "module": "root.lb_security_group.sg", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "nat", "type": "aws_eip", "count": 2, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "this", "type": "aws_internet_gateway", "count": 1, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "this", "type": "aws_nat_gateway", "count": 2, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "private_nat_gateway", "type": "aws_route", "count": 2, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "public_internet_gateway", "type": "aws_route", "count": 1, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "private", "type": "aws_route_table", "count": 2, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "public", "type": "aws_route_table", "count": 1, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "private", "type": "aws_route_table_association", "count": 2, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "public", "type": "aws_route_table_association", "count": 2, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "private", "type": "aws_subnet", "count": 2, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "public", "type": "aws_subnet", "count": 2, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] }, { "name": "this", "type": "aws_vpc", "count": 1, "module": "root.vpc", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "index-keys": [] } ], "resources-processed": true, "serial": 3, "state-version": 4, "status": "finalized", "terraform-version": "1.6.6", "vcs-commit-url": "None", "vcs-commit-sha": "None" }, "relationships": { "run": { "data": { "id": "run-9XBzSuHzfgwTZNak", "type": "runs" } }, "rollback-state-version": { "data": "None" }, "created-by": { "data": { "id": "user-VGzYMVaTX6nVYZ3U", "type": "users" }, "links": { "self": "/api/v2/users/user-VGzYMVaTX6nVYZ3U", "related": "/api/v2/runs/run-9XBzSuHzfgwTZNak/created-by" } }, "workspace": { "data": { "id": "ws-pN259iR1J3cW8ivr", "type": "workspaces" } }, "outputs": { "data": [ { "id": "wsout-uJCPifEGM1fC5UF1", "type": "state-version-outputs" }, { "id": "wsout-SD3QFXBkcK41c8Vq", "type": "state-version-outputs" }, { "id": "wsout-ryDfZJ4dwxuqR5NU", "type": "state-version-outputs" }, { "id": "wsout-5vnxsMuMaorwojVL", "type": "state-version-outputs" }, { "id": "wsout-cP7abYGPeXpgJfrt", "type": "state-version-outputs" } ], "links": { "related": "/api/v2/state-versions/sv-wZEoyjPg1KYsjYZg/outputs" } } }, "links": { "self": "/api/v2/state-versions/sv-wZEoyjPg1KYsjYZg" }, "__output": [ { "id": "wsout-syLHF4vVm1ELesRH", "type": "state-version-outputs", "attributes": { "name": "lb_url", "sensitive": false, "type": "string", "value": "http://lb-r4c-project-alpha-dev-81440499.eu-west-1.elb.amazonaws.com/", "detailed-type": "string" }, "links": { "self": "/api/v2/state-version-outputs/wsout-syLHF4vVm1ELesRH" } }, { "id": "wsout-yCuZCAT1MBrpW9rL", "type": "state-version-outputs", "attributes": { "name": "vpc_id", "sensitive": false, "type": "string", "value": "vpc-085a343aa3f06a9d7", "detailed-type": "string" }, "links": { "self": "/api/v2/state-version-outputs/wsout-yCuZCAT1MBrpW9rL" } }, { "id": "wsout-ujnf5q4ZS3LTEFmL", "type": "state-version-outputs", "attributes": { "name": "web_server_count", "sensitive": false, "type": "number", "value": 4, "detailed-type": "number" }, "links": { "self": "/api/v2/state-version-outputs/wsout-ujnf5q4ZS3LTEFmL" } } ] } ``` Port entityJSON ``` { "identifier": "sv-wZEoyjPg1KYsjYZg", "title": "sv-wZEoyjPg1KYsjYZg", "team": [], "properties": { "createdAt": "2023-12-20T07:08:14.113Z", "serial": 3, "status": "finalized", "hostedStateDownloadUrl": "https://app.terraform.io/api/state-versions/sv-wZEoyjPg1KYsjYZg/hosted_state", "hostedJsonDownloadUrl": "https://app.terraform.io/api/state-versions/sv-wZEoyjPg1KYsjYZg/hosted_json_state", "outputData": [ { "id": "wsout-syLHF4vVm1ELesRH", "type": "state-version-outputs", "attributes": { "name": "lb_url", "sensitive": false, "type": "string", "value": "http://lb-r4c-project-alpha-dev-81440499.eu-west-1.elb.amazonaws.com/", "detailed-type": "string" }, "links": { "self": "/api/v2/state-version-outputs/wsout-syLHF4vVm1ELesRH" } }, { "id": "wsout-yCuZCAT1MBrpW9rL", "type": "state-version-outputs", "attributes": { "name": "vpc_id", "sensitive": false, "type": "string", "value": "vpc-085a343aa3f06a9d7", "detailed-type": "string" }, "links": { "self": "/api/v2/state-version-outputs/wsout-yCuZCAT1MBrpW9rL" } }, { "id": "wsout-ujnf5q4ZS3LTEFmL", "type": "state-version-outputs", "attributes": { "name": "web_server_count", "sensitive": false, "type": "number", "value": 4, "detailed-type": "number" }, "links": { "self": "/api/v2/state-version-outputs/wsout-ujnf5q4ZS3LTEFmL" } } ], "size": 8554, "isResourcesProcessed": true }, "relations": {}, "icon": "Terraform" } ``` Default mappingYAML ``` - kind: state-file selector: query: 'true' port: entity: mappings: identifier: .lineage + "-" + (.serial | tostring) title: '"State v" + (.serial | tostring)' blueprint: '"terraformCloudStateFile"' properties: version: .version terraformVersion: .terraform_version serial: .serial lineage: .lineage resourceCount: .resources | length resourceTypes: .resources | map(.type) | unique | sort providers: .resources | map(.provider) | unique | sort outputKeys: .outputs | keys ``` Sample payloadJSON ``` { "version": 4, "terraform_version": "1.5.7", "serial": 1, "lineage": "7e30e20a-09d2-6e05-300a-e1875120359f", "outputs": {}, "resources": [ { "module": "module.vpc", "mode": "managed", "type": "aws_vpc", "name": "main", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 1, "attributes": { "cidr_block": "10.0.0.0/16", "enable_dns_hostnames": true, "id": "vpc-0abc123def456789" } } ] }, { "module": "module.vpc", "mode": "managed", "type": "aws_subnet", "name": "public", "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", "instances": [ { "schema_version": 1, "attributes": { "cidr_block": "10.0.1.0/24", "id": "subnet-0abc123def456789" } } ] } ] } ``` Port entityJSON ``` { "identifier": "7e30e20a-09d2-6e05-300a-e1875120359f-1", "title": "State v1", "blueprint": "terraformCloudStateFile", "properties": { "version": 4, "terraformVersion": "1.5.7", "serial": 1, "lineage": "7e30e20a-09d2-6e05-300a-e1875120359f", "resourceCount": 10, "resourceTypes": [ "google_cloud_asset_project_feed", "google_iam_role", "google_organization_iam_custom_role", "google_project_iam_binding", "google_projects", "google_pubsub_topic", "google_service_account" ], "providers": [ "provider[\"registry.terraform.io/hashicorp/google\"]" ], "outputKeys": [] }, "relations": {} } ``` Supported selectors Below are the supported [selectors](/context-lake/ingestion/configure-mapping/overview/.md#selector) for this resource. Use them under the `selector` key in your integration mapping configuration to control which resources are returned from the API. | Selector | Type | Description | | -------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------- | | `exportEnvVariables` | `array` | Environment variable names whose values should be included in lakehouse ingest payloads for DSP processing. | | `currentOnly` | `boolean` | If true, fetch only the current state file per workspace. If false, fetch all historical state files. Default: `true`. | Default mappingYAML ``` - kind: health-assessment selector: query: 'true' port: entity: mappings: identifier: .id title: .type + "-" + .id blueprint: '"terraformCloudHealthAssessment"' properties: createdAt: .attributes."created-at" succeeded: .attributes.succeeded allChecksSucceeded: .attributes."all-checks-succeeded" drifted: .attributes.drifted checksErrored: .attributes."checks-errored" checksFailed: .attributes."checks-failed" checksPassed: .attributes."checks-passed" checksUnknown: .attributes."checks-unknown" resourcesDrifted: .attributes."resources-drifted" resourcesUndrifted: .attributes."resources-undrifted" errorMessage: .attributes."error-message" relations: workspace: .relationships.workspace.data.id ``` Sample payloadJSON ``` { "id": "asmtres-66efb162c1f1", "type": "assessment-results", "attributes": { "succeeded": true, "all-checks-succeeded": true, "checks-errored": 0, "checks-failed": 0, "checks-passed": 0, "checks-unknown": 0, "created-at": "2026-02-13T11:40:28.530Z", "drifted": false, "error-message": null, "resources-drifted": 0, "resources-undrifted": 4 }, "relationships": { "workspace": { "data": { "id": "ws-71ac4fae", "type": "workspaces" } } }, "links": { "self": "/api/v2/assessment-results/asmtres-66efb162c1f1", "json-output": "/api/v2/assessment-results/asmtres-66efb162c1f1/json-output", "json-schema": "/api/v2/assessment-results/asmtres-66efb162c1f1/json-schema", "log-output": "/api/v2/assessment-results/asmtres-66efb162c1f1/log-output", "health-json-redacted": "/api/v2/assessment-results/asmtres-66efb162c1f1/health-json-redacted" } } ``` Port entityJSON ``` { "blueprint": "terraformCloudHealthAssessment", "identifier": "asmtres-66efb162c1f1", "title": "assessment-results-asmtres-66efb162c1f1", "properties": { "resourcesUndrifted": 4, "checksErrored": 0, "drifted": false, "resourcesDrifted": 0, "checksUnknown": 0, "errorMessage": null, "checksFailed": 0, "checksPassed": 0, "succeeded": true, "allChecksSucceeded": true }, "relations": { "workspace": "ws-71ac4fae" } } ``` ## Set up live events[​](#set-up-live-events "Direct link to 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. Supported live event triggers (click to expand) * Self-hosted ### BaseUrl & webhook configuration[​](#baseurl--webhook-configuration "Direct link to 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 Terraform Cloud 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 Terraform Cloud 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 as `liveEvents.baseUrl`. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. * **If Terraform Cloud is external to the integration's network**: The integration must be exposed via an ingress, load balancer, or public URL that Terraform Cloud can reach. Scaling for high-volume, self-hosted deployments If you're running high volumes of live events in a self-hosted deployment and want better reliability, event durability, or horizontal scalability, you can optionally set up Port's dedicated live events infrastructure. See [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) for details. 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`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#scheduled-resync) parameter has to be set, or a manual resync will need to be triggered through Port's UI. Debugging local integrations 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`) ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") To learn more about how to monitor and check the sync status of your integration, see the [relevant documentation](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status). ## Examples[​](#examples "Direct link to Examples") To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources).
Find the integration in the list of connectors and click on it to open the playground. Examples of blueprints and the relevant integration configurations can be found in the [examples page](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/terraform-cloud/examples/.md). ## Relevant Guides[​](#relevant-guides "Direct link to Relevant Guides") For relevant guides and examples, see the [guides section](https://docs.port.io/guides?tags=Terraform). --- # Examples To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your [connectors page](https://app.port.io/settings/data-sources). Find the integration in the list of connectors and click on it to open the playground. ## Organization[​](#organization "Direct link to Organization") **Organization blueprint (click to expand)** Create in Port ``` { "identifier": "terraformCloudOrganization", "description": "This blueprint represents an organization in Terraform Cloud", "title": "Terraform Cloud Organization", "icon": "Terraform", "schema": { "properties": { "externalId": { "type": "string", "title": "External ID", "description": "The external ID of the organization" }, "ownerEmail": { "type": "string", "title": "Owner Email", "description": "The email associated with the organization" }, "collaboratorAuthPolicy": { "type": "string", "title": "Collaborator Authentication Policy", "description": "Policy for collaborator authentication" }, "planExpired": { "type": "string", "title": "Plan Expired", "description": "Indicates if plan is expired" }, "planExpiresAt": { "type": "string", "format": "date-time", "title": "Plan Expiry Date", "description": "The data and time which the plan expires" }, "permissions": { "type": "object", "title": "Permissions", "description": "Permissions associated with the organization" }, "samlEnabled": { "type": "boolean", "title": "SAML Enabled", "description": "Indicates if SAML is enabled for the organization" }, "defaultExecutionMode": { "type": "string", "title": "Default Execution Mode", "description": "The default execution mode for the organization" } } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` - kind: organization selector: query: "true" port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudOrganization"' properties: externalId: .attributes."external-id" ownerEmail: .attributes.email collaboratorAuthPolicy: .attributes."collaborator-auth-policy" planExpired: .attributes."plan-expired" planExpiresAt: .attributes."plan-expires-at" permissions: .attributes.permissions samlEnabled: .attributes."saml-enabled" defaultExecutionMode: .attributes."default-execution-mode" ``` ## Project[​](#project "Direct link to Project") **Project blueprint (click to expand)** Create in Port ``` { "identifier": "terraformCloudProject", "description": "This blueprint represents a project in Terraform Cloud", "title": "Terraform Cloud Project", "icon": "Terraform", "schema": { "properties": { "name": { "type": "string", "title": "Project Name", "description": "The name of the Terraform Cloud project" }, "permissions": { "type": "object", "title": "Permissions", "description": "The permissions on the project" } } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "organization": { "title": "Terraform Cloud Organization", "target": "terraformCloudOrganization", "required": true, "many": false } } } ``` **Integration configuration (click to expand)** ``` - kind: project selector: query: "true" port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudProject"' properties: name: .attributes.name permissions: .attributes.permissions relations: organization: .relationships.organization.data.id ``` ## Workspace[​](#workspace "Direct link to Workspace") **Workspace blueprint (click to expand)** Create in Port ``` { "identifier": "terraformCloudWorkspace", "description": "This blueprint represents a workspace in Terraform Cloud", "title": "Terraform Cloud Workspace", "icon": "Terraform", "schema": { "properties": { "organization": { "type": "string", "title": "Organization", "description": "The organization within which the workspace belongs to" }, "createdAt": { "type": "string", "format": "date-time", "title": "Creation Time", "description": "The creation timestamp of the workspace" }, "updatedAt": { "type": "string", "format": "date-time", "title": "Last Updated", "description": "The last update timestamp of the workspace" }, "terraformVersion": { "type": "string", "title": "Terraform Cloud Version", "description": "Version of Terraform cloud used by the workspace" }, "locked": { "type": "boolean", "title": "Locked Status", "description": "Indicates whether the workspace is locked" }, "executionMode": { "type": "string", "title": "Execution Mode", "description": "The execution mode of the workspace" }, "resourceCount": { "type": "number", "title": "Resource Count", "description": "Number of resources managed by the workspace" }, "latestChangeAt": { "type": "string", "format": "date-time", "title": "Latest Change", "description": "Timestamp of the latest change in the workspace" }, "tags": { "type": "array", "title": "Workspace Tags", "description": "Terraform workspace tags" } } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "currentStateVersion": { "title": "Current State Version", "target": "terraformCloudStateVersion", "required": false, "many": false }, "project": { "title": "Terraform Cloud Project", "target": "terraformCloudProject", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` - kind: workspace selector: query: "true" port: entity: mappings: identifier: .id title: .attributes.name blueprint: '"terraformCloudWorkspace"' properties: organization: .relationships.organization.data.id createdAt: .attributes."created-at" updatedAt: .attributes."updated-at" terraformVersion: .attributes."terraform-version" locked: .attributes.locked executionMode: .attributes."execution-mode" resourceCount: .attributes."resource-count" latestChangeAt: .attributes."latest-change-at" tags: .__tags relations: currentStateVersion: .relationships."current-state-version".data.id project: .relationships.project.data.id ``` ## Run[​](#run "Direct link to Run") **Run blueprint (click to expand)** Create in Port ``` { "identifier": "terraformCloudRun", "description": "This blueprint represents a run in Terraform cloud", "title": "Terraform Cloud Run", "icon": "Terraform", "schema": { "properties": { "createdAt": { "type": "string", "format": "date-time", "title": "Creation Time", "description": "The creation timestamp of the run" }, "status": { "type": "string", "title": "Run Status", "description": "The current status of the run" }, "hasChanges": { "type": "boolean", "title": "Has Changes", "description": "Indicates whether the run has changes" }, "isDestroy": { "type": "boolean", "title": "Is Destroy", "description": "Indicates whether the run is a destroy operation" }, "message": { "type": "string", "title": "Run Message", "description": "Message associated with the run" }, "terraformVersion": { "type": "string", "title": "Terraform Cloud Version", "description": "Version of Terraform cloud used in the run" }, "appliedAt": { "type": "string", "format": "date-time", "title": "Applied Time", "description": "Timestamp when the run was applied" }, "plannedAt": { "type": "string", "format": "date-time", "title": "Planned Time", "description": "Timestamp when the run was planned" }, "source": { "type": "string", "title": "Run Source", "description": "The source of the run initiation" } } }, "relations": { "terraformCloudWorkspace": { "title": "Terraform Cloud Workspace", "target": "terraformCloudWorkspace", "required": false, "many": false } } } ``` **Integration configuration (click to expand)** ``` - kind: run selector: query: "true" port: entity: mappings: identifier: .id title: .attributes.message blueprint: '"terraformCloudRun"' properties: createdAt: .attributes."created-at" status: .attributes.status hasChanges: .attributes."has-changes" isDestroy: .attributes."is-destroy" message: .attributes.message terraformVersion: .attributes."terraform-version" appliedAt: .attributes."status-timestamps"."applied-at" plannedAt: .attributes."status-timestamps"."planned-at" source: .attributes.source relations: terraformCloudWorkspace: .relationships.workspace.data.id ``` ## State Version[​](#state-version "Direct link to State Version") **State Version blueprint (click to expand)** Create in Port ``` { "identifier": "terraformCloudStateVersion", "description": "This blueprint represents a version of a Terraform state version", "title": "Terraform Cloud State Versions", "icon": "Terraform", "schema": { "properties": { "createdAt": { "type": "string", "format": "date-time", "title": "Creation Time", "description": "Timestamp when the state version was created" }, "serial": { "type": "number", "title": "Serial Number", "description": "A unique identifier for this version within the workspace" }, "status": { "type": "string", "title": "Status", "description": "The current status of the state version (e.g., 'queued', 'finished')" }, "size": { "type": "number", "title": "Size", "description": "The size of the resources" }, "isResourcesProcessed": { "type": "boolean", "title": "Is Resources Processed", "description": "Whethere the resources has been processed" }, "hostedStateDownloadUrl": { "type": "string", "title": "Download Url", "format": "url", "description": "Hosted state version download url " }, "hostedJsonDownloadUrl": { "type": "string", "title": "Download Url", "format": "url", "description": "Url for downloading state version in json format" }, "outputData": { "type": "array", "title": "Output", "description": "output returned from state version" }, "vcsCommitUrl": { "type": "string", "title": "VCS Commit URL", "format": "url", "description": "URL of the VCS commit that triggered this state version" } } }, "relations": {}, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {} } ``` **Integration configuration (click to expand)** ``` - kind: state-version selector: query: "true" port: entity: mappings: identifier: .id title: .id blueprint: '"terraformCloudStateVersion"' properties: createdAt: .attributes."created-at" serial: .attributes.serial status: .attributes.status size: .attributes.size isResourcesProcessed: .attributes."resources-processed" hostedStateDownloadUrl: .attributes."hosted-state-download-url" hostedJsonDownloadUrl: .attributes."hosted-json-state-download-url" vcsCommitUrl: .attributes."vcs-commit-url" outputData: .__output ``` ## State file[​](#state-file "Direct link to State file") **State file blueprint (click to expand)** Create in Port ``` { "identifier": "terraformCloudStateFile", "description": "This blueprint represents a Terraform state file summary", "title": "Terraform Cloud State File", "icon": "Terraform", "schema": { "properties": { "version": { "type": "number", "title": "State Version", "description": "The version of the state file format" }, "terraformVersion": { "type": "string", "title": "Terraform Version", "description": "The version of Terraform used to create this state" }, "serial": { "type": "number", "title": "Serial Number", "description": "A unique serial number that increments with each state change" }, "lineage": { "type": "string", "title": "Lineage UUID", "description": "A unique identifier for the state lineage" }, "resourceCount": { "type": "number", "title": "Total Resources", "description": "The total number of resources in the state" }, "resourceTypes": { "type": "array", "title": "Resource Types", "description": "List of unique resource types in the state", "items": { "type": "string" } }, "providers": { "type": "array", "title": "Providers Used", "description": "List of providers used in this state", "items": { "type": "string" } }, "outputKeys": { "type": "array", "title": "Output Keys", "description": "List of output variable names defined in the state", "items": { "type": "string" } } } }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": {} } ``` **Integration configuration (click to expand)** ``` - kind: state-file selector: query: "true" port: entity: mappings: identifier: .lineage + "-" + (.serial | tostring) title: '"State v" + (.serial | tostring)' blueprint: '"terraformCloudStateFile"' properties: version: .version terraformVersion: .terraform_version serial: .serial lineage: .lineage resourceCount: .resources | length resourceTypes: .resources | map(.type) | unique | sort providers: .resources | map(.provider) | unique | sort outputKeys: .outputs | keys ``` #### Current only[​](#current-only "Direct link to Current only") You can use the `currentOnly` selector to control how state files are fetched. By default, only the current state file is fetched per workspace, optimizing sync time and API usage. ``` - kind: state-file selector: query: "true" currentOnly: false ``` ## Health assessment[​](#health-assessment "Direct link to Health assessment") Ingesting health assessment results HCP Terraform requires organizations to be on a paid plan (Standard and Premium) to opt in for health assessments. For more information on assessment permissions and requirements, see the [Terraform Cloud documentation](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/health). **Health assessment blueprint (click to expand)** Create in Port ``` { "identifier": "terraformCloudHealthAssessment", "description": "This blueprint represents a Terraform Cloud health assessment result", "title": "Terraform Cloud Health Assessment", "icon": "Terraform", "schema": { "properties": { "createdAt": { "type": "string", "format": "date-time", "title": "Created At", "description": "Timestamp when the assessment result was created" }, "drifted": { "type": "boolean", "title": "Drifted", "description": "Whether drift was detected (actual infrastructure differs from desired state)" }, "succeeded": { "type": "boolean", "title": "Succeeded", "description": "Whether the overall health assessment completed successfully" }, "allChecksSucceeded": { "type": "boolean", "title": "All Checks Succeeded", "description": "Whether all continuous validation checks passed successfully" }, "checksErrored": { "type": "number", "title": "Checks Errored", "description": "Number of resources/data sources/outputs with condition errors" }, "checksFailed": { "type": "number", "title": "Checks Failed", "description": "Number of resources/data sources/outputs with one or more failing conditions" }, "checksPassed": { "type": "number", "title": "Checks Passed", "description": "Number of resources/data sources/outputs where all conditions passed" }, "checksUnknown": { "type": "number", "title": "Checks Unknown", "description": "Number of resources/data sources/outputs with unknown check status" }, "resourcesDrifted": { "type": "number", "title": "Resources Drifted", "description": "Number of resources that have drifted" }, "resourcesUndrifted": { "type": "number", "title": "Resources Undrifted", "description": "Number of resources that match the expected state (no drift)" }, "errorMessage": { "type": "string", "title": "Error Message", "description": "Error message if the assessment failed" } }, "required": [] }, "mirrorProperties": {}, "calculationProperties": {}, "aggregationProperties": {}, "relations": { "workspace": { "title": "Terraform Cloud Workspace", "description": "The Terraform Cloud workspace this assessment result belongs to", "target": "terraformCloudWorkspace", "required": true, "many": false } } } ``` **Integration configuration (click to expand)** ``` - kind: health-assessment selector: query: 'true' port: entity: mappings: identifier: .id title: .type + "-" + .id blueprint: '"terraformCloudHealthAssessment"' properties: createdAt: .attributes."created-at" succeeded: .attributes.succeeded allChecksSucceeded: .attributes."all-checks-succeeded" drifted: .attributes.drifted checksErrored: .attributes."checks-errored" checksFailed: .attributes."checks-failed" checksPassed: .attributes."checks-passed" checksUnknown: .attributes."checks-unknown" resourcesDrifted: .attributes."resources-drifted" resourcesUndrifted: .attributes."resources-undrifted" errorMessage: .attributes."error-message" relations: workspace: .relationships.workspace.data.id ``` --- # Actions processing Ocean integrations can execute [integration actions](/workflows/build-workflows/nodes/action-nodes/integration-actions/overview/.md), which write data or trigger operations in the third-party tool, such as dispatching a GitHub workflow or creating a Jira issue. This page describes how Ocean's actions processor picks up action runs from Port and executes them. The set of available actions is specific to each integration. By default, actions run with the integration's own credentials. To run them as the user who triggered the workflow, see [identity propagation](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/identity-propagation/.md). ## Execution flow[​](#execution-flow "Direct link to Execution flow") 1. **Trigger** - A user, automation, or workflow runs an integration action in Port. Port creates a pending action run. 2. **Claim** - The integration's actions processor polls Port for pending runs and claims a batch of them. A claimed run is hidden from other integration instances for a limited time, so the same run is not executed twice. 3. **Queue** - Ocean places each claimed run in a queue, as described in [queues and partitioning](#queues-and-partitioning). 4. **Execute** - A worker takes the run from its queue and executes the action against the third-party API. 5. **Report** - Ocean reports the run's logs, status label, and final result (success or failure) back to Port, where you can follow it on the run's page. Some actions hand work off to a long-running process in the third-party tool, such as a CI pipeline. In that case the run stays in progress until the integration receives a live event reporting that the process finished. ## Queues and partitioning[​](#queues-and-partitioning "Direct link to Queues and partitioning") Ocean uses two kinds of queues: * **Global queue** - Runs that can execute in parallel. * **Partition queues** - Runs that must execute one after another. Each action can define a partition key, for example the target repository. Runs with the same partition key execute sequentially, in order, to avoid conflicting changes. A pool of workers processes runs from all queues in a round-robin order, so a busy partition does not block the others. If the same run arrives twice while it is still being processed, Ocean skips the duplicate. ## Flow control[​](#flow-control "Direct link to Flow control") The actions processor protects both the integration and the third-party API from overload: * **Buffer limit** - Ocean stops claiming new runs when the total number of queued runs reaches `runsBufferHighWatermark`, and resumes once workers free up space. * **Per-action limit** - A single action cannot fill more than `maxRunsBufferUtilPctPerAction` percent of the buffer, so a burst of runs for one action does not starve the others. * **Rate limiting** - Before executing a run, Ocean checks whether the third-party API is close to its rate limit. If it is, Ocean pauses execution until the limit resets, backing off for up to 10 seconds at a time. ## Status labels[​](#status-labels "Direct link to Status labels") While a run is in progress, the integration can attach a short status label, such as `Dispatching workflow` or `Workflow running`, which Port displays next to the run. A failed run always ends with a label. If the integration does not provide a specific one, Ocean sets the label to `Execution failed`. ## What an action looks like in code[​](#what-an-action-looks-like-in-code "Direct link to What an action looks like in code") An integration implements each action as an **executor**, a class that extends Ocean's `AbstractExecutor`. The examples below are adapted from the [Jira](https://github.com/port-labs/ocean/tree/main/integrations/jira/jira/actions), [GitHub](https://github.com/port-labs/ocean/tree/main/integrations/github/github/actions), and [Azure DevOps](https://github.com/port-labs/ocean/tree/main/integrations/azure-devops/azure_devops/actions) integrations. ### Declare the action[​](#declare-the-action "Direct link to Declare the action") The integration declares its actions and their inputs in its `.port/spec.json` file. Port uses this declaration to show the action and its inputs in the workflow builder: .port/spec.json ``` { "actionsProcessingEnabled": true, "actions": [ { "name": "add_comment", "description": "Add a comment to a Jira issue", "inputs": [ { "name": "issueKey", "title": "Issue key", "type": "string", "required": true }, { "name": "comment", "title": "Comment", "type": "string", "required": true } ] } ] } ``` ### Implement the executor[​](#implement-the-executor "Direct link to Implement the executor") The executor's `ACTION_NAME` matches the action's `name` in the spec. The inputs sent from Port are available in `run.execution_properties`: jira/actions/add\_comment\_executor.py ``` from port_ocean.context.ocean import ocean from port_ocean.core.handlers.actions.abstract_executor import AbstractExecutor from port_ocean.core.models import IntegrationRun, WorkflowNodeRun class AddCommentExecutor(AbstractExecutor): ACTION_NAME = "add_comment" WEBHOOK_PROCESSOR_CLASS = None async def is_close_to_rate_limit(self, run: IntegrationRun) -> bool: return False async def get_remaining_seconds_until_rate_limit(self, run: IntegrationRun) -> float: return 0.0 async def execute(self, run: IntegrationRun) -> None: issue_key = run.execution_properties["issueKey"] # Log to the run in Port, and set its status label await ocean.port_client.post_run_log( run, f"Adding comment to Jira issue {issue_key}", status_label="Adding comment", ) comment = await self.client.add_comment( issue_key, run.execution_properties["comment"] ) # Expose an output to the next nodes in the workflow if isinstance(run, WorkflowNodeRun): run.output = {"issueKey": issue_key, "commentId": comment.id} # Mark the run as successful await ocean.port_client.report_run_completed( run, success=True, message=f"Added comment to {issue_key}", status_label="Comment added", ) ``` If `execute` raises an exception, Ocean marks the run as failed and reports the error message to Port. To set a specific status label for the failure, raise an `ActionExecutionError`: ``` from port_ocean.exceptions.execution_manager import ActionExecutionError class AddCommentError(ActionExecutionError): DEFAULT_STATUS_LABEL = "Comment failed" ``` Executors are registered once, when the integration starts: main.py ``` ocean.register_action_executor(AddCommentExecutor()) ``` ### Partition keys and rate limits[​](#partition-keys-and-rate-limits "Direct link to Partition keys and rate limits") The [GitHub integration](https://github.com/port-labs/ocean/blob/main/integrations/github/github/actions/abstract_github_executor.py) runs actions on the same repository one after another, and pauses execution when its GitHub API quota is running low: github/actions/abstract\_github\_executor.py ``` class AbstractGithubExecutor(AbstractExecutor): async def _get_partition_key(self, run: IntegrationRun) -> str | None: # Runs with the same key execute sequentially. None means "run in parallel". org = run.execution_properties.get("org") repo = run.execution_properties.get("repo") if not isinstance(org, str) or not isinstance(repo, str): return None return f"{org}/{repo}" async def is_close_to_rate_limit(self, run: IntegrationRun) -> bool: info = (await self._get_rest_client(run)).get_rate_limit_status() return bool(info) and info.remaining < MIN_REMAINING_RATE_LIMIT_FOR_ACTIONS async def get_remaining_seconds_until_rate_limit(self, run: IntegrationRun) -> float: info = (await self._get_rest_client(run)).get_rate_limit_status() return info.seconds_until_reset if info else 0.0 ``` ### Long-running actions[​](#long-running-actions "Direct link to Long-running actions") When an action starts a long-running process, the executor marks the run as started and returns. A live events processor reports the result once the third-party tool sends a webhook about it. The [Azure DevOps](https://github.com/port-labs/ocean/tree/main/integrations/azure-devops/azure_devops/actions) `trigger_pipeline` action links the pipeline run to the Port run using an external ID: azure\_devops/actions/trigger\_pipeline\_executor.py ``` class TriggerPipelineExecutor(AbstractExecutor): ACTION_NAME = "trigger_pipeline" # The processor that reports completion, and the endpoint it listens on WEBHOOK_PROCESSOR_CLASS = PipelineRunActionWebhookProcessor WEBHOOK_PATH = "/webhook" async def execute(self, run: IntegrationRun) -> None: pipeline_run = await self.client.run_pipeline(project_id, pipeline_id, options) external_id = build_external_id(project_id, pipeline_id, pipeline_run["id"]) link = pipeline_run["_links"]["web"]["href"] # The run stays in progress in Port, with a link to the pipeline run await ocean.port_client.update_run_started(run, link, external_id) ``` azure\_devops/webhooks/webhook\_processors/pipeline\_run\_action\_webhook\_processor.py ``` class PipelineRunActionWebhookProcessor(AbstractWebhookProcessor): @classmethod def get_processor_type(cls) -> WebhookProcessorType: # An ACTION processor updates runs, not catalog entities return WebhookProcessorType.ACTION async def handle_event(self, payload, resource_config) -> WebhookEventRawResults: run = payload["resource"]["run"] external_id = build_external_id(project_id, pipeline_id, run["id"]) # Find the Port run that started this pipeline run, and complete it port_run = await ocean.port_client.find_run_by_external_id(external_id) if port_run and ocean.port_client.is_run_in_progress(port_run): await ocean.port_client.report_run_completed( port_run, run["result"] == "succeeded", f"Pipeline run {run['result']}" ) return WebhookEventRawResults(updated_raw_results=[], deleted_raw_results=[]) ``` ## Configuration[​](#configuration "Direct link to Configuration") Action processing is disabled by default. To enable it, set `actionsProcessor.enabled` to `true` (`OCEAN__ACTIONS_PROCESSOR__ENABLED` environment variable). The integration's [event listener](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#event-listener-types) must also support actions. ``` actionsProcessor: enabled: true workersCount: 3 runsBufferHighWatermark: 300 pollCheckIntervalSeconds: 10 visibilityTimeoutMs: 60000 maxRunsBufferUtilPctPerAction: 30 ``` | Parameter | Environment variable | Description | Default | | ------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------- | | `enabled` | `OCEAN__ACTIONS_PROCESSOR__ENABLED` | Whether to start the actions processor and poll Port for pending runs. | `false` | | `workersCount` | `OCEAN__ACTIONS_PROCESSOR__WORKERS_COUNT` | The number of concurrent workers. Tune according to the CPU and memory allocated to the integration. | `3` | | `runsBufferHighWatermark` | `OCEAN__ACTIONS_PROCESSOR__RUNS_BUFFER_HIGH_WATERMARK` | The maximum number of queued runs before Ocean stops claiming new ones (1-1000). | `300` | | `pollCheckIntervalSeconds` | `OCEAN__ACTIONS_PROCESSOR__POLL_CHECK_INTERVAL_SECONDS` | The time in seconds between polls for pending runs. | `10` | | `visibilityTimeoutMs` | `OCEAN__ACTIONS_PROCESSOR__VISIBILITY_TIMEOUT_MS` | The time in milliseconds a claimed run stays hidden from other consumers before it can be claimed again (1-600000). | `60000` | | `maxRunsBufferUtilPctPerAction` | `OCEAN__ACTIONS_PROCESSOR__MAX_RUNS_BUFFER_UTIL_PCT_PER_ACTION` | The maximum percentage of the buffer a single action can use before Ocean stops claiming its runs (1-100). | `30` | On shutdown, Ocean stops claiming new runs and waits up to `maxWaitSecondsBeforeShutdown` seconds (`OCEAN__MAX_WAIT_SECONDS_BEFORE_SHUTDOWN`, default `5`) for workers to finish their current runs. --- # Advanced configuration Self-hosted Ocean integrations often run behind a load balancer, a corporate proxy, or a firewall. This page describes the settings that control how the integration is exposed on the network and how it sends requests to Port and to the third-party tool. ## Integration endpoints[​](#integration-endpoints "Direct link to Integration endpoints") An Ocean integration runs an HTTP server, on port `8000` by default (`APPLICATION__PORT` environment variable). It exposes the following endpoints: | Endpoint | Description | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /health/live` | Liveness check. Returns `200` while the integration process is running. | | `GET /health/ready` | Readiness check. Returns `503` until the integration finishes starting up, then `200`. | | `GET /metrics` | Prometheus metrics for the integration's resyncs. Populated when `OCEAN__METRICS__ENABLED` is `true`. | | `/integration/*` | The integration's [live events](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/live-events-processing/.md) endpoints. | | `POST /resync` | Triggers a resync. Exposed only when using the [`WEBHOOK` event listener](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md#webhook). | Both health endpoints return a JSON body that includes the integration's Ocean core version, for example: ``` { "status": "healthy", "check": "ready", "core_version": "0.52.3" } ``` Use `/health/ready` for Kubernetes readiness probes, so traffic is routed to the pod only after it has started, and `/health/live` for liveness probes. ## Base URL and path prefix[​](#base-url-and-path-prefix "Direct link to Base URL and path prefix") | Variable | Description | Default | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | `OCEAN__BASE_URL` | The public URL of the integration. Required for live events, since the integration uses it to register webhooks in the third-party tool. | - | | `OCEAN__PATH_PREFIX` | A prefix added to the integration's health, metrics, and live events endpoints. Use it when the integration is served under a sub-path of a shared host. | - | For example, with `OCEAN__PATH_PREFIX=/github`, the readiness endpoint becomes `/github/health/ready`, and the prefix is appended to the base URL used for webhooks. When `OCEAN__BASE_URL` is not set, the integration does not process live events, and data is updated only by resyncs. Deprecated setting `OCEAN__INTEGRATION__CONFIG__APP_HOST` is deprecated. Use `OCEAN__BASE_URL` instead. Ocean uses the [HTTPX](https://www.python-httpx.org/) client for HTTP requests, so the standard HTTPX environment variables apply to it. ## Proxy configuration[​](#proxy-configuration "Direct link to Proxy configuration") Use the following environment variables to route the integration's requests through a proxy server: | Variable | Description | | ------------- | ------------------------------------------------------------------ | | `HTTP_PROXY` | The proxy URL for HTTP requests. | | `HTTPS_PROXY` | The proxy URL for HTTPS requests. | | `ALL_PROXY` | The proxy URL for all requests. | | `NO_PROXY` | A comma-separated list of hostnames or URLs that bypass the proxy. | For example: ``` HTTP_PROXY=http://my-proxy.com:1111 HTTPS_PROXY=http://my-proxy.com:2222 NO_PROXY=127.0.0.1,internal.example.com ``` For more details, see the HTTPX [proxy documentation](https://www.python-httpx.org/environment_variables/#proxies). ## Custom certificates[​](#custom-certificates "Direct link to Custom certificates") Use the following environment variables when the integration needs to trust a private certificate authority (CA), for example one used by an SSL inspection proxy or a self-hosted tool: | Variable | Description | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `SSL_CERT_FILE` | The path to a file containing a bundle of trusted CA certificates in PEM format. | | `SSL_CERT_DIR` | The path to a directory containing trusted CA certificate files. | | `SSLKEYLOGFILE` | The path to a file where TLS key information is logged. Use it only for debugging, to decrypt traffic with tools such as Wireshark. | For example: ``` SSL_CERT_FILE=/path/to/cacert.pem ``` For more details, see the HTTPX [SSL documentation](https://www.python-httpx.org/environment_variables/#sslkeylogfile). ## Ocean SSL verification settings[​](#ocean-ssl-verification-settings "Direct link to Ocean SSL verification settings") Ocean provides separate TLS verification settings for traffic to Port's API and traffic to third-party APIs (GitLab, Jira, GitHub, and others). These settings are useful when your CA is trusted, but Python 3.13+ still rejects the certificate because of its strict X.509 rules. This commonly happens behind SSL inspection proxies that issue certificates without an Authority Key Identifier. | Variable | Default | Applies to | | --------------------------------------- | ------- | ----------------- | | `OCEAN__SSL__PORT__VERIFY` | `true` | Port's API. | | `OCEAN__SSL__PORT__X509__STRICT` | `true` | Port's API. | | `OCEAN__SSL__THIRD_PARTY__VERIFY` | `true` | Third-party APIs. | | `OCEAN__SSL__THIRD_PARTY__X509__STRICT` | `true` | Third-party APIs. | The combination of the two settings determines the behavior: | `verify` | `x509.strict` | Behavior | | -------- | ------------- | ------------------------------------------------------------------------------------------- | | `true` | `true` | Default. Full certificate validation, the same as standard Python TLS. | | `true` | `false` | Validates the certificate chain and hostname, but relaxes Python 3.13+ strict X.509 checks. | | `false` | Ignored | Disables all TLS verification. Use only as a temporary last resort. | If TLS to a third-party API fails behind a firewall that re-signs certificates, start with: ``` OCEAN__SSL__THIRD_PARTY__X509__STRICT=false ``` Change the Port API settings only if requests to Port also fail. Security considerations * If your private CA is not in the system trust store, configure `SSL_CERT_FILE` or `SSL_CERT_DIR` first. The Ocean SSL settings do not replace trusting your CA. * Prefer `x509.strict=false` over `verify=false`, which disables all protection. Do not use `verify=false` in production without your security team's approval. * Ocean logs a warning at startup whenever a non-default SSL setting is active. ### Supported integrations[​](#supported-integrations "Direct link to Supported integrations") The `OCEAN__SSL__THIRD_PARTY__*` settings apply to integrations that use Ocean's built-in HTTP client, including GitLab, GitHub, Jira, Azure DevOps, and Snyk. They do not apply to integrations that use a vendor SDK (AWS, Azure, GCP, Kafka, New Relic, and the deprecated GitLab integration), to the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md), which has its own `verify_ssl` setting, or to ArgoCD when `allow_insecure` is set to `true`. ## HTTP client timeouts and retries[​](#http-client-timeouts-and-retries "Direct link to HTTP client timeouts and retries") Ocean's built-in HTTP client handles timeouts and retries for requests to Port and to third-party APIs. * **Timeout** - Each request times out after `OCEAN__CLIENT_TIMEOUT` seconds (default `60`). * **Retried status codes** - `401`, `408`, `429`, `502`, `503`, and `504`. Integrations can add status codes specific to their API. * **Retried methods** - Only idempotent methods: `GET`, `HEAD`, `PUT`, `DELETE`, `OPTIONS`, and `TRACE`. `POST` and `PATCH` requests are not retried. * **Backoff** - Up to 10 attempts with exponential backoff and jitter, waiting at most 60 seconds between attempts. * **Rate limits** - When the response includes a `Retry-After` or `x-ratelimit-reset` header, Ocean waits for the time it specifies instead of the default backoff. * **Network errors** - Connection errors and timeouts are retried the same way. If requests to a slow self-hosted tool time out, increase `OCEAN__CLIENT_TIMEOUT`. ## Outbound IP blocker[​](#outbound-ip-blocker "Direct link to Outbound IP blocker") To protect against server-side request forgery (SSRF), Ocean can block requests whose host resolves to a non-public IP address. This includes private ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback, link-local addresses such as cloud metadata endpoints, and carrier-grade NAT ranges. Port's own domains and the APIs of supported third-party SaaS tools are always allowed. | Installation | Default | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Hosted by Port | Enabled. Integrations hosted by Port cannot reach tools on private networks. To integrate with a tool on your private network, [self-host the integration](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods). | | Self-hosted | Disabled. The integration can reach any address its network allows. | To enable the blocker on a self-hosted integration, set `OCEAN__DISABLE_IP_OUTBOUND_BLOCKER=false`. Blocked requests fail with an error stating that the host IP address is not within the allowed ranges. ## Streaming large responses[​](#streaming-large-responses "Direct link to Streaming large responses") Some integrations can stream large API responses to disk instead of loading them into memory, which reduces the integration's memory usage. Streaming is used only by integrations that support it, currently [ArgoCD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd/.md#configure-streaming), and is disabled by default. | Variable | Description | Default | | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ---------------------- | | `OCEAN__STREAMING__ENABLED` | Whether to stream large responses to disk. | `false` | | `OCEAN__STREAMING__CHUNK_SIZE` | The size of each chunk read from the response, in bytes. | `65536` (64 KB) | | `OCEAN__STREAMING__MAX_BUFFER_SIZE_MB` | The maximum size of the in-memory buffer used while parsing, in bytes despite its name. | `20971520` (20 MB) | | `OCEAN__STREAMING__LOCATION` | The directory where streamed data is temporarily stored. Data is encrypted on disk and deleted after it is read. | `/tmp/ocean/streaming` | --- # Connection probe The connection probe checks whether an integration's credentials can connect to the third-party tool, and whether they have the permissions required to sync each `kind`, without running a resync. Run it after installing a self-hosted integration or rotating its credentials, to find missing permissions before they cause a resync to fail. Supported integrations The connection probe is currently supported by the [GitHub](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md) and [Jira](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/.md) integrations. ## What the probe checks[​](#what-the-probe-checks "Direct link to What the probe checks") For each `kind` supported by the integration, the probe checks the permissions granted to the integration's credentials, and reports one of the following results: | Result | Meaning | | --------- | ------------------------------------------------------------------------ | | `SUCCESS` | All permissions required for the `kind` are granted. | | `FAILURE` | One or more required permissions are missing. The message lists them. | | `UNKNOWN` | The integration does not define the permissions required for the `kind`. | If the credentials cannot connect to the tool at all, the probe fails and exits with an error. For example, a successful probe of the Jira integration reports: ``` { "status": "COMPLETED", "probeMode": "shallow", "startedAt": "2026-09-29T10:00:00+00:00", "endedAt": "2026-09-29T10:00:02+00:00", "checks": [ { "kind": "project", "scopes": {}, "status": "SUCCESS", "message": "Basic Jira access verified" }, { "kind": "user", "scopes": {}, "status": "FAILURE", "message": "Missing permission(s): USER_PICKER" } ] } ``` ## What it looks like in code[​](#what-it-looks-like-in-code "Direct link to What it looks like in code") Each resource configuration in the integration declares the permissions its `kind` requires, using `probe_permissions`. The following snippets are adapted from the [Jira integration](https://github.com/port-labs/ocean/tree/main/integrations/jira/jira/probe): jira/overrides.py ``` from typing import ClassVar from port_ocean.core.handlers.port_app_config.models import ResourceConfig class JiraIssueConfig(ResourceConfig): probe_permissions: ClassVar[tuple[str, ...]] = ("BROWSE_PROJECTS",) class JiraUserResourceConfig(ResourceConfig): probe_permissions: ClassVar[tuple[str, ...]] = ("USER_PICKER",) ``` The integration registers a probe handler with `@ocean.on_probe()`. The handler verifies authentication, fetches the permissions granted to the credentials, and records a result for each `kind`: main.py ``` from port_ocean.core.probe import ProbeCheckStatus, ProbeContext @ocean.on_probe() async def probe(context: ProbeContext) -> ProbeContext: client = get_or_create_jira_client() try: await client.verify_current_user() except (HTTPStatusError, RequestError): await context.fail("Failed to verify Jira authentication.") return context # One check per kind being probed checks = await context.setup_unscoped_checks() granted = await client.get_current_user_permissions(required_permissions) for check in checks: # Compares the kind's probe_permissions with the granted permissions check.status, check.message = permission_verdict.verdict(check.kind, granted) await context.update_progress() return context ``` ## Run the probe[​](#run-the-probe "Direct link to Run the probe") The probe is run with the `ocean probe` command, which is included in the integration's Docker image. Run the image with the same environment variables you use for the integration, and override its command: ``` docker run --rm \ --env-file ./integration.env \ ghcr.io/port-labs/port-ocean-jira:latest \ ocean probe --kinds project,issue ``` The command exits with a non-zero status code if the probe fails, so it can also be used as a check in CI pipelines. The following options are available: | Option | Description | Default | | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | | `--kinds` | A comma-separated list of the `kinds` to check. | All kinds supported by the integration | | `--reporting-mode` | Where to write the results: `log` prints them to the output, and `file` writes them as JSON to a `probe_reports` directory under the integration's path. | `log` | | `--log-level` | The log level: `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `CRITICAL`. | `INFO` | When using `--reporting-mode file`, mount a volume at `/app/probe_reports` to keep the report after the container exits. --- # Event listeners and resync triggers An event listener is the component that tells an Ocean integration **when** to run a resync. It receives requests from Port, such as a mapping change or a manual resync, and starts a resync in the integration. The event listener also determines whether the integration processes live events and actions, as described in [event listener types](#event-listener-types) below. ## Resync triggers[​](#resync-triggers "Direct link to Resync triggers") An Ocean integration starts a full resync in the following cases: * **On startup** - When the integration starts, if the event listener is configured to resync on start. * **Configuration change** - When the integration's configuration or mapping is updated in Port, for example from the [connectors](https://app.port.io/settings/data-sources) page. * **Manual resync** - When you click **Resync** on the connectors page, or call the [resync API endpoint](/api-reference/resync-an-integration/). * **Scheduled resync** - At a fixed interval, if [`scheduledResyncInterval`](#scheduled-resync) is set. Before a resync starts, Ocean reports the resync as running in Port. When the resync ends, Ocean reports it as completed or failed, and this status appears on the integration's [sync status](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status) page. Overlapping resyncs If a new full resync is triggered while another is still running, the running resync is aborted and the new one starts. If resyncs keep aborting each other, the integration never completes a full resync. See [sync interactions](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/sync-interactions/.md) for details. ## Event listener types[​](#event-listener-types "Direct link to Event listener types") The event listener is configured using the `eventListener.type` parameter (`OCEAN__EVENT_LISTENER__TYPE` environment variable). The default is `POLLING`. | Type | How resync requests arrive | Resyncs | Live events | Actions | | --------------------- | --------------------------------------------------------------------------------- | ------- | ----------- | ------- | | [`POLLING`](#polling) | The integration queries Port at a fixed interval. | ✅ | ✅ | ✅ | | [`KAFKA`](#kafka) | The integration consumes messages from your organization's dedicated Kafka topic. | ✅ | ✅ | ✅ | | [`WEBHOOK`](#webhook) | Port sends an HTTP request to the integration. | ✅ | ✅ | ✅ | | [`ONCE`](#once) | The integration runs a single resync and exits. | ✅ | ❌ | ❌ | | `WEBHOOKS_ONLY` | None. The integration only processes live events. | ❌ | ✅ | ❌ | | `ACTIONS_ONLY` | None. The integration only processes action runs. | ❌ | ❌ | ✅ | Multiple instances Event listeners that trigger resyncs do not support running multiple instances of the same integration. To scale live events processing horizontally, see [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale). ### POLLING[​](#polling "Direct link to POLLING") The integration queries Port at a fixed interval and checks whether the configuration changed or a resync was requested. If so, it starts a resync. ``` eventListener: type: POLLING resyncOnStart: true interval: 60 ``` | Parameter | Description | Default | | --------------- | ---------------------------------------------------- | ------- | | `resyncOnStart` | Whether to run a resync when the integration starts. | `true` | | `interval` | The time in seconds between queries to Port. | `60` | The polling listener requires only outbound traffic from the integration to Port, which makes it the simplest option for most self-hosted deployments. ### KAFKA[​](#kafka "Direct link to KAFKA") The integration consumes resync requests from a Kafka topic dedicated to your organization. When the configuration is updated in Port, Port publishes a message to the topic and the integration starts a resync. ``` eventListener: type: KAFKA ``` | Parameter | Description | Default | | ------------------------- | --------------------------------------------------------------------------------------- | -------------------- | | `brokers` | A comma-separated list of Kafka brokers. | Port's Kafka brokers | | `securityProtocol` | The security protocol used to connect to the brokers. | `SASL_SSL` | | `authenticationMechanism` | The authentication mechanism used to connect to the brokers. | `SCRAM-SHA-512` | | `kafkaSecurityEnabled` | Whether to authenticate to the brokers with your organization's Kafka credentials. | `true` | | `consumerPollTimeout` | The time in seconds the consumer waits for messages before returning an empty response. | `1` | The defaults connect to Port's managed Kafka brokers, so you usually do not need to change them. Your organization's Kafka credentials are fetched from Port automatically. ### WEBHOOK[​](#webhook "Direct link to WEBHOOK") Port sends an HTTP `POST` request to the integration's `/resync` endpoint whenever the configuration is updated or a resync is requested. ``` eventListener: type: WEBHOOK appHost: "https://my-ocean-integration.example.com" ``` | Parameter | Description | Default | | --------- | ---------------------------------------------- | ------- | | `appHost` | The URL of the integration, reachable by Port. | - | The `appHost` URL must accept inbound requests from Port. Port's requests originate from a fixed list of IP addresses, listed in Port's [security documentation](/platform-administration/security/.md). ### ONCE[​](#once "Direct link to ONCE") The integration runs a single resync and then exits. This listener is used to run integrations as scheduled jobs, for example from a [CI pipeline](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods) or a Kubernetes CronJob. ``` eventListener: type: ONCE ``` When [incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync/.md) is enabled for the integration, a `ONCE` run performs an incremental sync instead of a full resync. ## Scheduled resync[​](#scheduled-resync "Direct link to Scheduled resync") The `scheduledResyncInterval` parameter (`OCEAN__SCHEDULED_RESYNC_INTERVAL` environment variable) sets the number of minutes between automatic full resyncs. * When set, the integration fetches fresh data at this interval, regardless of configuration changes. * When not set, resyncs run only when triggered by the event listener. Scheduled resyncs work alongside any event listener that supports resyncs. For hosted integrations, you select the resync interval in Port when installing the integration. Choosing an interval Set an interval long enough for a full resync to complete. Otherwise, the next scheduled resync aborts the running one. Start with a large interval, such as 2 hours, and reduce it only if you need more frequent updates. --- # Identity propagation By default, an Ocean integration executes [integration actions](/workflows/build-workflows/nodes/action-nodes/integration-actions/overview/.md) using its own credentials. Every pipeline run or pull request it creates appears in the third-party tool as the integration's service account. With identity propagation, an integration action runs **as the user who triggered the workflow**, using that user's own credentials in the third-party tool. The tool's audit logs show the real user, and the action is limited to that user's permissions. Supported actions Identity propagation is currently supported by the Azure DevOps [Trigger a pipeline](/workflows/build-workflows/nodes/action-nodes/integration-actions/git/azure-devops/.md#trigger-a-pipeline) action, in self-hosted Azure DevOps integrations. ## How it works[​](#how-it-works "Direct link to How it works") 1. **Trigger** - A user runs a workflow from a self-service trigger. The workflow's integration action node has identity propagation enabled. 2. **Sign** - Port attaches a short-lived identity token to the action run, signed by Port. The token identifies the user and the specific run. 3. **Verify** - The integration verifies the token against Port's public signing keys, and confirms that it belongs to the run it received. 4. **Look up** - The integration looks up the user's access token for the third-party tool in a vault that you own. 5. **Authorize (first run only)** - If no token is stored for the user, the run pauses and waits for the user to sign in. The user signs in to the third-party tool through the integration's OAuth broker, the integration stores the new token in the vault, and the run resumes automatically. 6. **Execute** - The integration executes the action using the user's token. Stored tokens are refreshed automatically when they expire. If a token cannot be refreshed, for example because the user revoked access, the run pauses and asks the user to sign in again. User tokens are stored only in your vault and never pass through Port. ## What it looks like in code[​](#what-it-looks-like-in-code "Direct link to What it looks like in code") An executor supports identity propagation by asking Ocean for the user's token before calling the third-party API. The following snippet is adapted from the Azure DevOps [`trigger_pipeline` executor](https://github.com/port-labs/ocean/blob/main/integrations/azure-devops/azure_devops/actions/trigger_pipeline_executor.py): azure\_devops/actions/trigger\_pipeline\_executor.py ``` from port_ocean.identity_propagation.token_exchanger import resolve_user_token class TriggerPipelineExecutor(AbstractExecutor): ACTION_NAME = "trigger_pipeline" async def execute(self, run: IntegrationRun) -> None: # Returns None when the run does not use identity propagation. # Verifies the identity token, reads the user's token from the vault, and # refreshes it if needed. Pauses the run if the user has to sign in. user_token = await resolve_user_token(run) client = self._client_for_token(user_token) if user_token else self.client pipeline_run = await client.run_pipeline(project_id, pipeline_id, options) ``` The integration also registers its OAuth provider on startup. The [Azure DevOps registry](https://github.com/port-labs/ocean/blob/main/integrations/azure-devops/azure_devops/oauth/registry.py) points Ocean's OAuth broker to Microsoft Entra ID: azure\_devops/oauth/registry.py ``` from port_ocean.identity_propagation.oauth_broker.providers import ProviderDefaults def register_oauth_provider() -> None: settings = ocean.config.identity_propagation.oauth.azure_devops if settings is None: return ocean.register_oauth_provider( defaults=ProviderDefaults( authorize_url="https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/authorize", token_url="https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token", # offline_access makes Entra ID issue a refresh token scopes=f"{ADO_RESOURCE_ID}/user_impersonation offline_access", ), settings=settings, ) ``` ## Prerequisites[​](#prerequisites "Direct link to Prerequisites") * **Self-service trigger** - Identity propagation requires a user to act as, so it is available only in workflows with a self-service trigger. Port rejects workflows that combine it with event or schedule triggers. * **Public base URL** - The integration's [`OCEAN__BASE_URL`](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md#base-url-and-path-prefix) must be reachable from your users' browsers, since the sign-in flow redirects them to it. On startup, the integration registers its OAuth broker with Port at `/v1/oauth-broker/authorize`. * **OAuth app** - An OAuth application registered in the third-party tool, with the redirect URI set to `/v1/oauth-broker/callback`. For Azure DevOps, register an application in Microsoft Entra ID. * **Vault** - An AWS Secrets Manager instance that the integration can access, with the `secretsmanager:GetSecretValue`, `secretsmanager:CreateSecret`, and `secretsmanager:PutSecretValue` permissions. ## Enable identity propagation in a workflow[​](#enable-identity-propagation-in-a-workflow "Direct link to Enable identity propagation in a workflow") Set `enableIdentityPropagation` to `true` in the configuration of the integration action node, in the workflow's JSON: ``` { "identifier": "trigger_pipeline", "title": "Trigger pipeline", "config": { "type": "INTEGRATION_ACTION", "installationId": "my-azure-devops-integration", "integrationProvider": "azure-devops", "integrationInvocationType": "trigger_pipeline", "enableIdentityPropagation": true, "integrationActionExecutionProperties": { "...": "..." } } } ``` ## Configuration[​](#configuration "Direct link to Configuration") Identity propagation is configured on the integration using the following environment variables. | Variable | Description | Default | | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | | `OCEAN__IDENTITY_PROPAGATION__ENABLED` | Whether to enable identity propagation. | `false` | | `OCEAN__IDENTITY_PROPAGATION__VAULT__TYPE` | The vault used to store user tokens. | `aws_secrets_manager` | | `OCEAN__IDENTITY_PROPAGATION__VAULT__AWS_REGION` | The AWS region of the vault. AWS credentials are read from the standard AWS credential chain. | - | | `OCEAN__IDENTITY_PROPAGATION__VAULT__SECRET_PREFIX` | The prefix of the secrets that hold user tokens. Each secret is named `///`. | `port/tokens` | | `OCEAN__IDENTITY_PROPAGATION__OAUTH__STATE_SIGNING_SECRET` | The secret used to sign the sign-in flow's state. When not set, a key is derived from the integration's Port client secret. | - | | `OCEAN__PORT__APP_URL` | The URL of Port's app, used to redirect users back to the workflow run after signing in. When not set, it is derived from the Port API URL. | - | For Azure DevOps, configure the OAuth application with the following variables: | Variable | Description | Default | | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | | `OCEAN__IDENTITY_PROPAGATION__OAUTH__AZURE_DEVOPS__CLIENT_ID` | The client ID of the Entra ID application. | - | | `OCEAN__IDENTITY_PROPAGATION__OAUTH__AZURE_DEVOPS__CLIENT_SECRET` | The client secret of the Entra ID application. | - | | `OCEAN__IDENTITY_PROPAGATION__OAUTH__AZURE_DEVOPS__TENANT_ID` | Your Entra ID tenant ID. | - | | `OCEAN__IDENTITY_PROPAGATION__OAUTH__AZURE_DEVOPS__SCOPES` | The OAuth scopes to request. The default includes `offline_access`, so users do not need to sign in again every hour. | Azure DevOps `user_impersonation` and `offline_access` | Actions without identity propagation support Enabling `enableIdentityPropagation` on an action that does not support it has no effect, and the action runs with the integration's own credentials. --- # Live events processing [Live events](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md) keep the catalog up to date between resyncs. This page describes what happens inside an Ocean integration when a third-party tool sends it an event, usually as a webhook. Which events are supported, and how to register the webhook in the third-party tool, is specific to each integration and documented on its page. ## Live events flow[​](#live-events-flow "Direct link to Live events flow") 1. **Receive** - The third-party tool sends an HTTP `POST` request to one of the integration's live events endpoints. Ocean exposes these endpoints under the `/integration` path prefix, for example `/integration/webhook`. 2. **Queue** - Ocean adds the event to a queue and responds to the sender immediately, so slow processing never causes the third-party tool to time out. 3. **Route** - Ocean passes the event to every live events processor registered for that endpoint. Each processor decides whether it handles the event, and which `kinds` the event affects. 4. **Authenticate and validate** - The processor verifies that the request is legitimate, for example by checking a webhook signature, and that the payload has the expected structure. Events that fail either check are rejected. 5. **Fetch** - The processor usually fetches the latest version of the resource from the third-party API, since webhook payloads often contain only partial data. 6. **Send to Port** - Ocean sends the updated and deleted resources to Port, which applies your mapping and updates the catalog. Kinds must be in the mapping Ocean processes a live event only for `kinds` that exist in the integration's [mapping](/context-lake/ingestion/configure-mapping/overview/.md). If you remove a `kind` from the mapping, live events for it are ignored. ## What a live events processor looks like[​](#what-a-live-events-processor-looks-like "Direct link to What a live events processor looks like") Each step of the flow above maps to a method of a live events processor. The following processor is adapted from the [Jira integration](https://github.com/port-labs/ocean/blob/main/integrations/jira/webhook_processors/project_webhook_processor.py), and handles Jira project events: webhook\_processors/project\_webhook\_processor.py ``` from port_ocean.core.handlers.port_app_config.models import ResourceConfig from port_ocean.core.handlers.webhook.abstract_webhook_processor import ( AbstractWebhookProcessor, ) from port_ocean.core.handlers.webhook.webhook_event import ( EventHeaders, EventPayload, WebhookEvent, WebhookEventRawResults, ) class ProjectWebhookProcessor(AbstractWebhookProcessor): # Route: does this processor handle the event? async def should_process_event(self, event: WebhookEvent) -> bool: return event.payload.get("webhookEvent", "").startswith("project_") # Route: which kinds in the mapping does the event affect? async def get_matching_kinds(self, event: WebhookEvent) -> list[str]: return ["project"] # Authenticate and validate: reject illegitimate or malformed requests. async def authenticate(self, payload: EventPayload, headers: EventHeaders) -> bool: return True async def validate_payload(self, payload: EventPayload) -> bool: return "project" in payload # Fetch: get the latest version of the resource from the Jira API. async def handle_event( self, payload: EventPayload, resource_config: ResourceConfig ) -> WebhookEventRawResults: project_key = payload["project"]["key"] if payload["webhookEvent"] == "project_soft_deleted": return WebhookEventRawResults( updated_raw_results=[], deleted_raw_results=[payload["project"]], ) project = await get_or_create_jira_client().get_single_project(project_key) return WebhookEventRawResults( updated_raw_results=[project], deleted_raw_results=[], ) ``` Ocean then sends the `updated_raw_results` and `deleted_raw_results` to Port, which maps them using the mapping of each matching `kind`. Processors are registered in `main.py`. Several processors can share the same endpoint, and Ocean passes each event to all of them: main.py ``` ocean.add_webhook_processor("/webhook", IssueWebhookProcessor) ocean.add_webhook_processor("/webhook", ProjectWebhookProcessor) ``` Integrations that support webhook signatures verify them in `authenticate`. For example, the [GitHub integration](https://github.com/port-labs/ocean/blob/main/integrations/github/github/webhook/webhook_processors/github_abstract_webhook_processor.py) compares the `x-hub-signature-256` header with an HMAC SHA-256 of the payload, computed with the webhook secret. ## Queuing and workers[​](#queuing-and-workers "Direct link to Queuing and workers") Each live events endpoint has its own queue. Ocean processes events from each queue using a pool of workers, configured with the `eventWorkersCount` parameter (`OCEAN__EVENT_WORKERS_COUNT` environment variable). The default is `1`, which processes events from each endpoint one at a time. Increasing the number of workers processes events in parallel, at the cost of more concurrent calls to the third-party API. When [live events at scale](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md#live-events-at-scale) is enabled, events are consumed from a Redis stream instead of the in-memory queue. To process events in parallel in this mode, scale the number of consumer pods. ## Retries and timeouts[​](#retries-and-timeouts "Direct link to Retries and timeouts") If processing an event fails with a retryable error, Ocean retries it with exponential backoff. By default, each event is retried up to 3 times, starting with a 1-second delay and capped at 30 seconds between attempts. Each integration decides which errors are retryable, and can override these values. A processor overrides the retry behavior with class attributes and the `should_retry` method. By default, only errors of type `RetryableError` are retried: ``` import httpx class ProjectWebhookProcessor(AbstractWebhookProcessor): max_retries = 5 initial_retry_delay_seconds = 2.0 max_retry_delay_seconds = 60.0 def should_retry(self, error: Exception) -> bool: # Keep the default behavior, and also retry when Jira returns a 5xx error if super().should_retry(error): return True return ( isinstance(error, httpx.HTTPStatusError) and error.response.status_code >= 500 ) ``` Each event has a processing time limit, including retries, set by the `maxEventProcessingSeconds` parameter (`OCEAN__MAX_EVENT_PROCESSING_SECONDS` environment variable, default `90`). Events that exceed it fail. When the integration shuts down, Ocean cancels in-flight events and waits up to `maxWaitSecondsBeforeShutdown` seconds (`OCEAN__MAX_WAIT_SECONDS_BEFORE_SHUTDOWN`, default `5`) for them to stop. ## Live events and resyncs[​](#live-events-and-resyncs "Direct link to Live events and resyncs") Live events are processed independently of resyncs. They keep running while a full resync is in progress, and a resync never waits for live events to finish. For how Port reconciles entities updated by both, see [sync interactions](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/sync-interactions/.md#live-events-behavior-during-a-full-resync). --- # Ocean framework overview Most of Port's [native integrations](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md), as well as the [No-code Ocean custom integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md), are built on [Ocean](https://github.com/port-labs/ocean), Port's open source integration framework. This section explains the mechanisms Ocean provides to every integration built on it. It is intended for readers who want to understand what happens inside an integration, for example when tuning a self-hosted deployment or troubleshooting sync behavior. ## What is Ocean?[​](#what-is-ocean "Direct link to What is Ocean?") Ocean is a Python framework made of two parts: | Component | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Ocean core** | A shared library (`port-ocean` on PyPI) that handles everything common to all integrations: communicating with Port, listening for resync requests, receiving live events, and executing actions. | | **Integrations** | Individual applications (GitHub, Jira, AWS, and more) that use Ocean core and implement only the logic specific to one third-party tool, such as its API client and the data each `kind` returns. | Because the core is shared, the mechanisms described in this section behave the same way across all Ocean integrations. Integration-specific behavior, such as which `kinds` and live events are supported, is documented on each integration's page. ## How an Ocean integration runs[​](#how-an-ocean-integration-runs "Direct link to How an Ocean integration runs") When an Ocean integration starts, it goes through the following steps: 1. **Load configuration** - Ocean reads the integration configuration from environment variables and Port, and validates it against the integration's specification. 2. **Connect to Port** - Ocean authenticates with Port using the integration's client credentials. 3. **Create default resources** - If `initializePortResources` is `true` (default), Ocean creates the integration's default blueprints and mapping in Port. 4. **Run startup hooks** - The integration runs its own startup logic, for example registering webhooks in the third-party tool. 5. **Start the event listener** - Ocean starts the configured [event listener](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md), which decides when to run a resync. 6. **Start background processors** - If enabled, Ocean starts the [live events processor](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/live-events-processing/.md) and the [actions processor](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/actions-processing/.md). From this point, the integration keeps running and reacts to three types of events: | Event | Source | Handled by | | -------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | Resync request | Port (configuration change, manual resync, or schedule) | [Event listeners](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/event-listeners/.md) | | Live event | The third-party tool (usually a webhook) | [Live events processing](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/live-events-processing/.md) | | Action run | Port (a user or workflow runs an integration action) | [Actions processing](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/actions-processing/.md) | To check an integration's credentials and permissions without running a resync, use the [connection probe](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/connection-probe/.md). For network, endpoint, and HTTP client settings, see [advanced configuration](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/advanced-configuration/.md). ## What an Ocean integration looks like[​](#what-an-ocean-integration-looks-like "Direct link to What an Ocean integration looks like") An Ocean integration is a Python application that registers handlers with Ocean core. The following `main.py` is adapted from the [Jira integration](https://github.com/port-labs/ocean/blob/main/integrations/jira/main.py), and shows how each mechanism in this section is wired: main.py ``` from port_ocean.context.ocean import ocean from port_ocean.core.ocean_types import ASYNC_GENERATOR_RESYNC_TYPE # Resync: called for each kind in the mapping, yields raw data in batches. @ocean.on_resync("project") async def on_resync_projects(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE: client = get_or_create_jira_client() async for projects in client.get_paginated_projects(): yield projects # Startup hook: runs once, for example to register webhooks in Jira. @ocean.on_start() async def on_start() -> None: if ocean.event_listener_type == "ONCE": return # A ONCE run exits after the resync, so it needs no webhooks await setup_application() # Connection probe: checks credentials and permissions without a resync. @ocean.on_probe() async def probe(context: ProbeContext) -> ProbeContext: await JiraPermissionProbe(context).run() return context # Live events: processors registered for the /integration/webhook endpoint. ocean.add_webhook_processor("/webhook", IssueWebhookProcessor) ocean.add_webhook_processor("/webhook", ProjectWebhookProcessor) # Actions: one executor per integration action. ocean.register_action_executor(CreateIssueExecutor()) ocean.register_action_executor(AddCommentExecutor()) ``` Everything else, such as polling Port for resync requests, queuing live events, claiming action runs, retries, and reporting status to Port, is handled by Ocean core. ## What Ocean does and what Port does[​](#what-ocean-does-and-what-port-does "Direct link to What Ocean does and what Port does") During a resync, Ocean calls the integration's code for each `kind` in the mapping. The integration fetches the data from the third-party API in batches, and Ocean sends the raw data to Port. Port then applies your [mapping configuration](/context-lake/ingestion/configure-mapping/overview/.md) to the raw data, ingests the resulting entities into the catalog, and runs reconciliation to delete stale entities. Ocean reports the resync's progress to Port throughout, so you can follow it on the integration's [sync status](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status) page. Live events follow the same split: Ocean receives the event and fetches the latest data from the third-party tool, and Port maps and ingests it. Mapping and reconciliation Mapping, ingestion, and reconciliation are documented in the [configure mapping](/context-lake/ingestion/configure-mapping/overview/.md) and [sync mechanisms](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md) sections. ## Build your own Ocean integration[​](#build-your-own-ocean-integration "Direct link to Build your own Ocean integration") To build an integration from scratch using Ocean, see [custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/overview/.md). --- # Install a native integration Port is designed to be flexible. It can be integrated with any tool, and can be used to build a software catalog that reflects your exact data model. Port comes with a wide variety of available integrations for popular tools and platforms, modeled and configured by us to provide you with a plug & play experience. Once installed, you can customize and extend these integrations to fit your exact needs. custom integrations Don't see the tool you wish to integrate with Port in the available integrations below?
Port provides you with the required tools to model your data and ingest it with ease, see [`Create a custom integration`](/context-lake/ingestion/ingest-data-into-port/custom-integration/overview/.md). ## Integration process[​](#integration-process "Direct link to Integration process") ![Data flow from external tools through an integration and mapping into the Port software catalog](/img/build-your-software-catalog/sync-data-to-catalog/integration_diagram.png)

Generally, installing an available integration results in the following: 1. The integration's default **data model** is created for you: 1. Default [blueprints]() will be created in the [Data model](https://app.port.io/settings/data-model) page in Port. 2. Default [mapping](/context-lake/ingestion/configure-mapping/overview/.md) will be defined in the integration's [connector](https://app.getport.io/settings/data-sources) page. 2. Data from the integrated tool is ingested into Port.
The sync mechanism and frequency depend on the installation method. After the installation, you can customize the blueprints and/or mapping to ingest more/less data (see below). ## Available plug & play integrations[​](#available-plug--play-integrations "Direct link to Available plug & play integrations") Search integrations... All64Git7Kubernetes stack8Argo CD1Cloud providers4Project management3CI/CD2Feature management1APM & alerting6Incident management8Code quality & security8Terraform1Cloud cost2Event processing1AI usage metrics4Automation platforms2Identity providers2Other4 [![GitHub icon](/img/guides/icons/GitHub.svg)![GitHub icon](/img/guides/icons/dark/GitHub.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md) ### [GitHub (Ocean)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md) [![GitHub icon](/img/guides/icons/GitHub.svg)![GitHub icon](/img/guides/icons/dark/GitHub.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) ### [GitHub (sunset)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/) [![GitLab icon](/img/guides/icons/GitLab.svg)![GitLab icon](/img/guides/icons/GitLab.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md) ### [GitLab v2](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab-v2/.md) [![GitLab icon](/img/guides/icons/GitLab.svg)![GitLab icon](/img/guides/icons/GitLab.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md) ### [GitLab (deprecated)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md) [![BitBucket icon](/img/guides/icons/BitBucket.svg)![BitBucket icon](/img/guides/icons/BitBucket.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/.md) ### [BitBucket](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-cloud/.md) [![BitBucket icon](/img/guides/icons/BitBucket.svg)![BitBucket icon](/img/guides/icons/BitBucket.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/.md) ### [BitBucket server](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/bitbucket/bitbucket-server/.md) [![AzureDevops icon](/img/guides/icons/AzureDevops.svg)![AzureDevops icon](/img/guides/icons/AzureDevops.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md) ### [AzureDevops](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md) [![Jenkins icon](/img/guides/icons/Jenkins.svg)![Jenkins icon](/img/guides/icons/Jenkins.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/jenkins/.md) ### [Jenkins](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/jenkins/.md) [![Octopus Deploy icon](/img/guides/icons/OctopusDeploy.svg)![Octopus Deploy icon](/img/guides/icons/OctopusDeploy.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/.md) ### [Octopus Deploy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/.md) [![ArgoCD icon](/img/guides/icons/ArgoCD.svg)![ArgoCD icon](/img/guides/icons/ArgoCD.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd/.md) ### [ArgoCD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/argocd/.md) [![Komodor icon](/img/guides/icons/Komodor.svg)![Komodor icon](/img/guides/icons/Komodor.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/komodor/.md) ### [Komodor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/komodor/.md) [![Kubernetes icon](/img/guides/icons/Kubernetes.svg)![Kubernetes icon](/img/guides/icons/Kubernetes.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) ### [Kubernetes](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/.md) [![Istio icon](/img/guides/icons/Istio.svg)![Istio icon](/img/guides/icons/Istio.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/istio/.md) ### [Istio](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/istio/.md) [![FluxCD icon](/img/guides/icons/FluxCD.svg)![FluxCD icon](/img/guides/icons/FluxCD.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/fluxcd/.md) ### [FluxCD](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/fluxcd/.md) [![Knative icon](/img/guides/icons/Knative.svg)![Knative icon](/img/guides/icons/Knative.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/knative/.md) ### [Knative](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/knative/.md) [![Kyverno icon](/img/guides/icons/Kyverno.svg)![Kyverno icon](/img/guides/icons/Kyverno.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/kyverno/.md) ### [Kyverno](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/kyverno/.md) [![OpenShift icon](/img/guides/icons/OpenShift.svg)![OpenShift icon](/img/guides/icons/OpenShift.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/openshift/.md) ### [OpenShift](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/openshift/.md) [![Trivy icon](/img/guides/icons/Trivy.svg)![Trivy icon](/img/guides/icons/Trivy.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/trivy/.md) ### [Trivy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/kubernetes-stack/kubernetes/templates/trivy/.md) [![Jira icon](/img/guides/icons/Jira.svg)![Jira icon](/img/guides/icons/Jira.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/.md) ### [Jira](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira/.md) [![Jira icon](/img/guides/icons/Jira.svg)![Jira icon](/img/guides/icons/Jira.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/.md) ### [Jira server (self-hosted)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/jira-server/.md) [![Linear icon](/img/guides/icons/Linear.svg)![Linear icon](/img/guides/icons/dark/Linear.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/linear/.md) ### [Linear](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/project-management/linear/.md) [![AWS icon](/img/guides/icons/AWS.svg)![AWS icon](/img/guides/icons/dark/AWS.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) ### [AWS Legacy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) [![AWS icon](/img/guides/icons/AWS.svg)![AWS icon](/img/guides/icons/dark/AWS.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/Overview/.md) ### [AWS](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/Overview/.md) [![Azure icon](/img/guides/icons/Azure.svg)![Azure icon](/img/guides/icons/Azure.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/.md) ### [Azure](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/.md) [![GCP icon](/img/guides/icons/GCP.svg)![GCP icon](/img/guides/icons/GCP.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/.md) ### [GCP](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/gcp/.md) [![Terraform icon](/img/guides/icons/Terraform.svg)![Terraform icon](/img/guides/icons/Terraform.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/terraform-cloud/.md) ### [Terraform](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/terraform-cloud/.md) [![Datadog icon](/img/guides/icons/Datadog.svg)![Datadog icon](/img/guides/icons/dark/Datadog.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/.md) ### [Datadog](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/datadog/.md) [![New Relic icon](/img/guides/icons/NewRelic.svg)![New Relic icon](/img/guides/icons/NewRelic.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/newrelic/.md) ### [New Relic](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/newrelic/.md) [![Sentry icon](/img/guides/icons/Sentry.svg)![Sentry icon](/img/guides/icons/dark/Sentry.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/sentry/.md) ### [Sentry](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/sentry/.md) [![Prometheus icon](/img/guides/icons/Prometheus.svg)![Prometheus icon](/img/guides/icons/Prometheus.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/prometheus/.md) ### [Prometheus](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/prometheus/.md) [![Dynatrace icon](/img/guides/icons/Dynatrace.svg)![Dynatrace icon](/img/guides/icons/Dynatrace.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/dynatrace/.md) ### [Dynatrace](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/dynatrace/.md) [![Azure icon](/img/guides/icons/Azure.svg)![Azure icon](/img/guides/icons/Azure.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/azure-monitor/.md) ### [Azure Monitor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/azure-monitor/.md) [![Snyk icon](/img/guides/icons/Snyk.svg)![Snyk icon](/img/guides/icons/Snyk.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/snyk/.md) ### [Snyk](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/snyk/.md) [![Wiz icon](/img/guides/icons/Wiz.svg)![Wiz icon](/img/guides/icons/Wiz.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/.md) ### [Wiz](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/wiz/.md) [![Mend icon](/img/guides/icons/Mend.svg)![Mend icon](/img/guides/icons/Mend.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/mend/.md) ### [Mend.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/mend/.md) [![SonarQube icon](/img/guides/icons/SonarQube.svg)![SonarQube icon](/img/guides/icons/SonarQube.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/.md) ### [SonarQube](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/sonarqube/.md) [![Aikido icon](/img/guides/icons/Aikido.svg)![Aikido icon](/img/guides/icons/Aikido.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/aikido/.md) ### [Aikido](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/aikido/.md) [![Checkmarx icon](/img/guides/icons/Checkmarx.svg)![Checkmarx icon](/img/guides/icons/Checkmarx.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/.md) ### [Checkmarx](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/checkmarx/.md) [![Armorcode icon](/img/guides/icons/Armorcode.svg)![Armorcode icon](/img/guides/icons/Armorcode.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/armorcode/.md) ### [Armorcode](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/armorcode/.md) [![BrowserStack icon](/img/guides/icons/BrowserStack.svg)![BrowserStack icon](/img/guides/icons/BrowserStack.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/browserstack/.md) ### [BrowserStack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/code-quality-security/browserstack/.md) [![Launchdarkly icon](/img/guides/icons/Launchdarkly.svg)![Launchdarkly icon](/img/guides/icons/dark/Launchdarkly.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/feature-management/launchdarkly/.md) ### [Launchdarkly](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/feature-management/launchdarkly/.md) [![PagerDuty icon](/img/guides/icons/PagerDuty.svg)![PagerDuty icon](/img/guides/icons/PagerDuty.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/.md) ### [PagerDuty](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/pagerduty/.md) [![StatusPage icon](/img/guides/icons/StatusPage.svg)![StatusPage icon](/img/guides/icons/StatusPage.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/.md) ### [StatusPage](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/statuspage/.md) [![FireHydrant icon](/img/guides/icons/FireHydrant.svg)![FireHydrant icon](/img/guides/icons/FireHydrant.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/firehydrant/.md) ### [FireHydrant](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/firehydrant/.md) [![OpsGenie icon](/img/guides/icons/OpsGenie.svg)![OpsGenie icon](/img/guides/icons/OpsGenie.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/.md) ### [OpsGenie](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/.md) [![ServiceNow icon](/img/guides/icons/ServiceNow.svg)![ServiceNow icon](/img/guides/icons/ServiceNow.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/servicenow/.md) ### [ServiceNow](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/servicenow/.md) [![Rootly icon](/img/guides/icons/Rootly.svg)![Rootly icon](/img/guides/icons/Rootly.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/rootly/.md) ### [Rootly](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/rootly/.md) [![IncidentIO icon](/img/guides/icons/IncidentIO.svg)![IncidentIO icon](/img/guides/icons/IncidentIO.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/incident-io/.md) ### [Incident.io](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/incident-io/.md) [![Jira icon](/img/guides/icons/Jira.svg)![Jira icon](/img/guides/icons/Jira.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/jira-service-management/.md) ### [Jira Service Management](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/jira-service-management/.md) [![Kubecost icon](/img/guides/icons/Kubecost.svg)![Kubecost icon](/img/guides/icons/Kubecost.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/.md) ### [Kubecost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/kubecost/.md) [![Opencost icon](/img/guides/icons/Opencost.svg)![Opencost icon](/img/guides/icons/Opencost.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/opencost/.md) ### [Opencost](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-cost/opencost/.md) [![Kafka icon](/img/guides/icons/Kafka.svg)![Kafka icon](/img/guides/icons/dark/Kafka.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/event-processing/kafka/.md) ### [Kafka](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/event-processing/kafka/.md) [![Okta icon](/img/guides/icons/Okta.svg)![Okta icon](/img/guides/icons/dark/Okta.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/okta/.md) ### [Okta](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/okta/.md) [![Azure icon](/img/guides/icons/Azure.svg)![Azure icon](/img/guides/icons/Azure.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/azure-ad/.md) ### [Microsoft Entra ID](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/azure-ad/.md) [![Amplication icon](/img/guides/icons/Amplication.svg)![Amplication icon](/img/guides/icons/Amplication.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/amplication/.md) ### [Amplication](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/amplication/.md) [![Backstage icon](/img/guides/icons/Backstage.svg)![Backstage icon](/img/guides/icons/dark/Backstage.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/backstage/.md) ### [Backstage](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/backstage/.md) [![Slack icon](/img/guides/icons/Slack.svg)![Slack icon](/img/guides/icons/Slack.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/slack/.md) ### [Slack](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/other/slack/.md) [![Zapier icon](/img/guides/icons/Zapier.svg)![Zapier icon](/img/guides/icons/Zapier.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/zapier/.md) ### [Zapier](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/zapier/.md) [![n8n icon](/img/guides/icons/n8n.svg)![n8n icon](/img/guides/icons/n8n.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/n8n/.md) ### [n8n](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/automation-platforms/n8n/.md) [![APIcatalog icon](/img/guides/icons/APIcatalog.svg)![APIcatalog icon](/img/guides/icons/APIcatalog.svg)](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) ### [Ocean custom](/context-lake/ingestion/ingest-data-into-port/custom-integration/ocean-custom-integration/overview/.md) [![Github Copilot icon](/img/guides/icons/GithubCopilot.svg)![Github Copilot icon](/img/guides/icons/dark/GithubCopilot.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/github-copilot/.md) ### [Github Copilot](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/github-copilot/.md) [![Claude AI icon](/img/guides/icons/ClaudeAI.svg)![Claude AI icon](/img/guides/icons/ClaudeAI.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/claude/.md) ### [Claude AI](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/claude/.md) [![Cursor icon](/img/guides/icons/Cursor.svg)![Cursor icon](/img/guides/icons/dark/Cursor.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/cursor/.md) ### [Cursor](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/cursor/.md) [![OpenAI icon](/img/guides/icons/OpenAI.svg)![OpenAI icon](/img/guides/icons/dark/OpenAI.svg)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/openai/.md) ### [OpenAI](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/ai-usage-metrics/openai/.md)
Ocean contribution Most of Port's integrations were developed using the **Ocean framework**.
Ocean is an open-source framework that simplifies the development of integrations with external tools, providing many out-of-the-box functionalities. Want to build a custom-coded Ocean integration? Start with the [custom-coded Ocean integration](/context-lake/ingestion/ingest-data-into-port/custom-integration/custom-coded-ocean-integration/overview/.md) docs. To contribute a public integration, see the [Ocean repository contributing guide](https://github.com/port-labs/ocean/blob/main/CONTRIBUTING.md). ## Integrations vs. MCP connectors[​](#integrations-vs-mcp-connectors "Direct link to Integrations vs. MCP connectors") Integrations are not the only way to connect external tools to Port. Port also supports **MCP connectors**, which give Port AI real-time access to a tool without syncing data into the catalog. **Use an integration** when you need persistent data in Port - for ownership, relations between resources, scorecards, or workflow automation. **Use an MCP connector** when you want Port AI to query a tool on demand without modeling its data. MCP connectors require minimal configuration and are a good starting point before committing to a full catalog integration. For many tools, you'll end up using both: the integration models the data you care about governing, while the MCP connector gives AI access to the tool's full surface area. **[Learn more: Integrations vs. MCP connectors →](/context-lake/ingestion/integrations-vs-mcp/.md)** ## Installation methods[​](#installation-methods "Direct link to Installation methods") Below is a breakdown of the supported installation methods for Port integrations. | Installation type | Security | Ease of installation | Periodic sync | Live sync (Webhook) | Infrastructure & maintenance responsibility | Use case fit | | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | -------------------- | ---------------------------------- | ------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | **Hosted by Port (OAuth)** | Port stores OAuth tokens securely. | 🟩🟩🟩🟩 | ✅
(user-selectable interval) | ✅
(Integration-dependent) | Port handles scaling, uptime, and updates. | Ideal for a quick setup with minimal effort. Mainly useful for getting started with an integration. | | **Hosted by Port (Custom settings)** | Port stores API credentials securely. Setup requires more details (e.g., token, host) and allows access control flexibility. | 🟩🟩🟩⬜ | ✅
(user-selectable interval) | ✅
(Integration-dependent) | Port handles scaling, uptime, and updates. | Best for quick setup without granting OAuth permissions. | | **Self-hosted** | Managed entirely in your infrastructure. | 🟩⬜⬜⬜ | ✅
(custom interval) | ✅
(Integration-dependent) | You provision, monitor, and maintain the integration. | Best for high-security or custom networking needs. | | **CI** | Managed in your CI/CD environment. | 🟩🟩⬜⬜ | ✅ (custom interval) | ❌ | Minimal infrastructure. You manage the CI/CD environment and triggers. | Best when real-time isn't needed and you want full sync control. | ## Sync mechanisms[​](#sync-mechanisms "Direct link to Sync mechanisms") Once an integration is installed, Port keeps its data up to date using one or more of the following sync mechanisms: * **[Full resync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md)** - Fetches all data from the third-party source on a schedule or on demand, maps it according to your configuration, and ingests it into Port. Includes reconciliation to delete stale entities and reingest dependents. * **[Incremental sync](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/incremental-sync/.md)** - Fetches only created or updated records since the last run, on a shorter interval than a typical full resync. Complements live events and reduces load on the data source. * **[Live events](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/live-events/.md)** - Processes source changes as they happen through webhooks or source-specific watch APIs, so mapped catalog entities update between scheduled resyncs. To learn how these mechanisms work inside the integration itself, see the [Ocean framework](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/overview/.md) section. ## Customize your integrations[​](#customize-your-integrations "Direct link to Customize your integrations") Now that you've installed an integration, let's see how you can customize it: 1. [**Configure your data model**](/context-lake/data-model/configure-data-model/.md) - Update the integration's data model in Port to ingest additional data that is not included by default. 2. [**Configure your data mapping**](/context-lake/ingestion/configure-mapping/overview/.md) - Update the integration's data mapping in Port to match the data model and fetch your desired data from the tool. ## Manage integrations via API[​](#manage-integrations-via-api "Direct link to Manage integrations via API") While integrations are installed using one of the methods above, you can manage existing integrations programmatically using Port's API. Available endpoints include: * [Get all integrations](/api-reference/get-all-integrations/) : List all integrations in your organization. * [Get an integration](/api-reference/get-an-integration/) : Fetch details of a specific integration. * [Update an integration](/api-reference/update-an-integration/) : Modify an integration's mapping, trigger a resync, or change its configuration. * [Delete an integration](/api-reference/delete-an-integration/) : Remove an integration from your organization. For more granular management, see also the endpoints for [integration config](/api-reference/update-an-integrations-config/), [event logs](/api-reference/get-an-integrations-event-logs/), [metrics and sync status](/api-reference/get-an-integrations-metrics-and-sync-status/), and [sync metadata](/api-reference/get-an-integrations-sync-metadata/). ## FAQ[​](#faq "Direct link to FAQ") 1What is the difference between an integration and an MCP connector? --- # Full resync A full resync is the default sync mechanism, and the one referred to as "periodic sync" in the [installation methods](/context-lake/ingestion/ingest-data-into-port/native-integrations/overview/.md#installation-methods) comparison table. On each run, the integration fetches all data available to it from the third-party source, maps it according to your [mapping configuration](/context-lake/ingestion/configure-mapping/overview/.md), and ingests it into Port. * The first sync after installing an integration is always a full resync. * It runs on the interval configured for your installation method (user-selectable for hosted integrations, custom for self-hosted and CI), or when triggered manually from the [connectors](https://app.port.io/settings/data-sources) page or the [resync API endpoint](/api-reference/resync-an-integration/). * It includes a reconciliation phase: entities that are no longer present in the data source are deleted, and entities that were waiting on related entities are reingested. * Full resync progress, per-`kind` status, and reconciliation results are visible in the [monitoring and sync status](#monitoring-and-sync-status) section below. ## Monitoring and sync status[​](#monitoring-and-sync-status "Direct link to Monitoring and sync status") Every integration has a dedicated page that displays detailed monitoring metrics and sync status, helping you track the data ingestion process. This monitoring view is available for all Ocean integrations. To access the integration's monitoring metrics and sync status, navigate to the [connectors](https://app.port.io/settings/data-sources) page. Under the `Exporters` section, select your integration. The `Sync Status` tab offers a detailed overview of the data ingestion and reconciliation processes. It updates on initial installation, manual resyncs, and at scheduled intervals. Note that it does not reflect changes from **live events**. It includes the following sections: ### Sync by kind[​](#sync-by-kind "Direct link to Sync by kind") A `kind` represents a specific type of resource or entity ingested by the integration. For example, if you are integrating with GitHub, a `kind` could be a repository, a pull request, or a commit. This section shows the monitoring metrics and sync status for each `kind`. When triggering a sync, the **Save & resync** button offers two options: * **Run full sync**: Runs all three stages below - fetches fresh data from the source, maps it, and ingests it into Port. The first sync is always a full resync. * **Apply mapping**: Skips the fetch stage and reruns only the mapping and ingestion using already-fetched data. This is useful after editing your [mapping configuration](/context-lake/ingestion/configure-mapping/overview/.md) - if you don't need fresh data from the source, apply mapping is much faster since it reuses existing data rather than re-fetching everything. **Sync stages overview**: * **Fetch data**: The first step in the data ingestion process is fetching the data from the data source. This stage only runs during a **Run full sync** and is skipped when using **Apply mapping**. In this section, you will find the following fields: * Fetched: Number of objects fetched from the data source. * Failed: If the fetch fails, this field will show `1` and `Fetched` will show `0`. Otherwise, it shows `0`. * **Map data**: The next step is mapping these objects according to the integration's [mapping configuration](https://docs.port.io/context-lake/ingestion/configure-mapping/overview). In this section, you will find the following fields: * Transformed: Number of objects that were mapped to blueprint entities. * Filtered out: Number of objects that were filtered out according to the mapping conditions. * Failed: Number of objects that failed to map, you can find more information about the failure reasons in the **Troubleshooting tips** section below in the UI or refer to the `Audit log` or `Event log` tabs, available in the same window. * **Ingest to Port**: Final step is ingesting the mapped objects to Port. In this section, you will find the following fields: * Ingested: Number of objects that were inserted or updated successfully. * Not changed: Number of objects that were already up to date, so no changes were made to them. * Failed: Number of objects that failed to ingest. To learn more about the failure reasons, see the `Audit log` or `Event log` tabs, available in the same window. **Details**: Displays the duration of the sync. #### Kind status[​](#kind-status "Direct link to Kind status") While a resync is in progress, each `kind` can show the following in-progress statuses: * **Pending**: The `kind` is waiting to start. * **Syncing**: The `kind` is currently being processed. After the ingestion process finishes for each `kind`, Port assigns a final status based on the metrics from each sync stage. Available final statuses: **Aborted**, **Failed**, **Synced with issues**, and **Synced**. A `kind` is marked as **Failed** when any of the following apply: * **Could not fetch data**: The fetch step reported failures and no objects were fetched. * **All objects failed to map**: At least one object was fetched, every fetched object failed to map, and none were transformed. * **Failed to ingest all mappable objects**: At least one object was fetched, not all objects were filtered out, not all objects failed to map, at least one object was transformed, and none were ingested while ingest reported failures. A `kind` is marked as **Synced with issues** when it is not **Failed**, and at least one sync stage reported failures. A `kind` is marked as **Synced** when it is not **Failed** and no sync stage reported failures. A `kind` is marked as **Aborted** when: * A newer sync of the same type starts before the current sync completes for that `kind`. * Metrics for the `kind` are not available (for example, the sync did not complete). ### Reconciliation[​](#reconciliation "Direct link to Reconciliation") This section provides visibility into both the reingestion and deletion processes. When the `createMissingRelatedEntities` flag is set to `false`, Port will **not** automatically create related entities that are referenced in the mapping but not yet present in Port. The reingestion process ensures that once the missing related entities are ingested, the dependent entities will be reingested. The deletion process ensures that entities that are no longer present in the data source are removed. When `createMissingRelatedEntities` is set to `true`, Port protects an auto-created related entity while a successfully ingested entity still references it. If the referencing entity fails to ingest and reconciliation continues, the related entity may be considered stale and deleted. Setting `deleteDependentEntities` to `false` does not prevent this cleanup. See [auto-created related entities during reconciliation](/context-lake/ingestion/configure-mapping/overview/.md#auto-created-related-entities-during-reconciliation) for preservation options. In this section, you will find the following data: **Ingestion retry**: * Reingested entities: Displays the number of entities that were ingested successfully on retry after dependencies resolved. * Failed: Displays the number of entities that failed to reingest. **Delete**: * Deleted entities: Displays the number of entities that were removed from Port to match the data source. * Failed: Displays the number of entities that failed to delete. **Details**: Displays the duration of the reconciliation. #### Reconciliation status[​](#reconciliation-status "Direct link to Reconciliation status") While a resync is in progress, reconciliation can show the following in-progress statuses: * **Pending**: Reconciliation is waiting to start. This happens when one or more `kind` ingestion processes have not finished yet. * **Syncing**: Reconciliation is currently running. After the reingestion and deletion processes finish, Port assigns a final reconciliation status based on the metrics from both stages and the status of each `kind`. Available final statuses: **Aborted**, **Failed**, **Synced with issues**, and **Synced**. Reconciliation is marked as **Aborted** when at least one `kind` is **Failed**. Reconciliation is marked as **Failed** when both stages fully failed: * **Ingestion retry** fully failed: At least one entity failed to reingest and none were reingested successfully. * **Delete** fully failed: At least one entity failed to delete and none were deleted successfully. Reconciliation is marked as **Synced with issues** when it is not **Aborted** or **Failed**, and any of the following apply: * **One stage fully succeeded, the other did not**: One stage completed with no failures and at least one success, while the other stage either fully failed or partially succeeded. * **Both stages partially succeeded**: Both **Ingestion retry** and **Delete** reported failures and successes. Reconciliation is marked as **Synced** when it is not **Aborted**, **Failed**, or **Synced with issues**. This includes cases where both stages completed with no failures, or when a stage had no work to perform. For how overlapping sync types interact (including apply mapping, incremental sync, and live events), see [Sync interactions](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/sync-interactions/.md). ### Sync status[​](#sync-status "Direct link to Sync status") While a sync is in progress, the integration shows the following in-progress statuses: * **Syncing**: The sync is currently running. After all `kind` ingestion processes and reconciliation finish, Port calculates a final overall resync status for the integration from the status of each `kind` and the reconciliation status. Available final statuses: **Aborted**, **Failed**, **Synced with issues**, and **Synced**. The resync is marked as **Failed** when every `kind` is **Failed**. The resync is marked as **Synced** when every `kind` is **Synced** and reconciliation is **Synced**. The resync is marked as **Synced with issues** when at least one `kind` is **Failed** or **Synced with issues**, or when reconciliation is **Failed** or **Synced with issues**. The resync is marked as **Aborted** when none of the above conditions apply. For example, when one or more `kind` statuses are **Aborted** and no `kind` or reconciliation issue triggered **Synced with issues**. **Note**: The same data is also available through the [Get an integration's metrics and sync status](https://docs.port.io/api-reference/get-an-integrations-metrics-and-sync-status) API endpoint. Prometheus Metrics Endpoint If you are using the **self-hosted integration method**, you can get raw Prometheus metrics by accessing the following endpoint: `{your_integration's_base_url}/metrics/`. **Understanding the metrics (click to expand)** **Types of Metrics Available** * **duration\_seconds**: Measures how long it takes for a phase to complete. *Labels*: `kind`, `phase` * **object\_count**: Counts the number of objects handled in each phase. *Labels*: `kind`, `phase`, `object_count_type` * **success** : Indicates whether the phase completed successfully. *Labels*: `kind`, `phase` **Types of Labels Available** * **kind**: The specific type of resource handled by the integration (varies per integration). * **phase**: The step in the ETL process: * `extract`: Fetch data from the source. * `transform`: Map or modify data before loading. * `load`: Ingest the data into Port. * `resync`: Full ETL process for a specific kind. * `delete`: Part of the reconciliation phase. * **object\_count\_type**: Subtype of object count, which varies by phase: * For `transform`: `transformed`, `filtered_out`, `failed`. * For `load`: `loaded`, `failed`, `skipped`. * For `extract`: `raw_extracted`, `failed`. * For `deletion`: `deleted`. ## FAQ[​](#faq "Direct link to FAQ") 1Which integrations support this view? 2Can I view previous syncs data? 3What are the troubleshooting tips based on? 4Does ingestion retry always attempt to process all failures? 5What happens if the same \`kind\` appears more than once in the mapping? --- # Incremental sync In addition to the full resync, some integrations support incremental sync. It runs **in parallel** to the full resync, on a shorter interval than a typical full resync. Instead of fetching all data from the third-party source, it fetches only what was created or updated since the last run. When a new incremental sync starts, Port aborts any in-progress incremental sync for that integration, but does not abort a full resync or apply mapping run happening in parallel. See [Sync interactions](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/sync-interactions/.md) for details. Note that incremental sync complements live events - it backfills changes that webhooks might miss, while the full resync still handles reconciliation. Incremental sync reuses the same mapping as the full resync, but only for the `kind`s that support it. Any other `kind` in your mapping is skipped by incremental sync and continues to be kept up to date by the full resync. #### Enable incremental sync For **Port-hosted integrations**: toggle incremental sync and set the interval in the `Advanced Configuration` section on the [connectors](https://app.port.io/settings/data-sources) page. For **self-hosted integrations**: configure `incremental_sync_enabled` and `incremental_sync_interval` in the integration settings. In both cases the interval must be 15, 30, or 60 minutes. The default is 15 minutes. For the list of `kind`s that support incremental sync for each integration, see the relevant section in that integration's page, for example [GitHub (Ocean)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github-ocean/.md#incremental-sync). #### Limitations * There is no reconciliation phase in incremental sync. Deletions are only detected and applied during a full resync. * Incremental sync runs are not yet shown in the event log or the [monitoring and sync status](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/full-resync/.md#monitoring-and-sync-status) view in the UI. The data is still available through the [Get an integration's metrics and sync status](/api-reference/get-an-integrations-metrics-and-sync-status/) and [Get an integration's event logs](/api-reference/get-an-integrations-event-logs/) API endpoints. * The first incremental run only looks back one interval - older changes rely on the preceding full resync. --- # Live events Live events allow an integration to process source changes as they happen, usually through webhooks or a source-specific watch API. They update mapped catalog entities between scheduled resyncs, which reduces the delay before changes appear in Port. Live events complement full and incremental syncs, but they do not replace full resync reconciliation. Support and setup vary by integration. If an integration does not support live events, Port keeps its data up to date through scheduled or manually triggered resyncs and, where available, incremental sync. Live events keep running while a full resync is in progress and are not stopped when other sync types start. For how live events interact with full resync reconciliation and other sync types, see [Sync interactions](/context-lake/ingestion/ingest-data-into-port/native-integrations/sync-mechanisms/sync-interactions/.md). For self-hosted deployments that require better reliability, event durability, or horizontal scaling of live event processing, you can optionally set up Port's dedicated live events infrastructure. ## Live events at scale[​](#live-events-at-scale "Direct link to Live events at scale") SaaS customers If you are using Port's hosted integrations, no action is required. The infrastructure described here is managed entirely by Port on the SaaS side. Port's live events infrastructure allows integrations to receive and process real-time webhook events from third-party services. For self-hosted deployments, you can configure a scalable infrastructure using Redis Streams and a dedicated ingestion component (`ocean-gateway`) to improve reliability, durability, and horizontal scalability. This section covers the components and configuration, deployment steps, and consumer scaling options for this infrastructure. Each integration's documentation remains the authoritative source for integration-specific setup (webhook registration, event types, and filters). ### Who is this for?[​](#who-is-this-for "Direct link to Who is this for?") This infrastructure is targeted at self-hosted (on-prem) customers who: * Run high volumes of live events and need better reliability and event ingestion guarantees. * Experience event loss on pod restarts or failure scenarios. * Want to scale live event consumers horizontally. ### Unsupported integrations[​](#unsupported-integrations "Direct link to Unsupported integrations") The live events at scale infrastructure does not support the following integrations: * [GitLab (deprecated)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/gitlab/.md). * [GitHub (Sunset)](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/github/). * [AWS Legacy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws/.md) (not [AWS](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/aws-v3/Overview/.md)). * [Opsgenie](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/incident-management/opsgenie/.md). * [Octopus Deploy](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cicd/octopus-deploy/.md). * [Dynatrace](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/apm-alerting/dynatrace/.md). * [Azure](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/cloud-providers/azure/.md). * [Okta](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/identity-providers/okta/.md). If you run one of these integrations in a self-hosted deployment, use the standard live events setup documented on each integration's page instead. ### How it works[​](#how-it-works "Direct link to How it works") Incoming webhook traffic is routed through your Ingress to `ocean-gateway`, which writes each event to a per-integration Redis Stream. Consumer pods read from that stream independently of ingestion, so events are never lost on a pod restart and consumers can be scaled horizontally. ![Live events infrastructure architecture showing webhook traffic routed through Ingress to ocean-gateway, which writes events to Redis Streams for consumer pods to process](/img/context-lake/ingestion/ingest-data-into-port/native-integrations/live-events-infrastructure-architecture.svg) ### Components and configuration[​](#components-and-configuration "Direct link to Components and configuration") This infrastructure is deployed via the `ocean-gateway` Helm chart, which can optionally bundle Redis or use a Redis instance you bring yourself. Once deployed, two values must be set for the whole flow to work: the live events base URL and your Ingress routing. #### Ocean-gateway[​](#ocean-gateway "Direct link to Ocean-gateway") The `ocean-gateway` service is a required component of the live events infrastructure. It handles all incoming webhook HTTP traffic and writes events to the appropriate Redis Stream, with no processing logic of its own. * It is available as an open-source service in the [ocean-gateway repository](https://github.com/port-labs/ocean-gateway). * It is included in the [Ocean Helm chart](https://github.com/port-labs/helm-charts) for self-hosted deployments and must be deployed alongside your integration pods. * Because it only ingests events into Redis Streams, it continues to accept and persist webhook traffic even when your live event consumer pods are unavailable, events wait in the stream and are processed once a consumer is back up. #### Redis[​](#redis "Direct link to Redis") * Minimum version: Redis 7.0 or later. * Redis Streams support is required. It is available from Redis 5.0, but version 7.0 or later is required for full compatibility. Live events are stored in Redis Streams. The `ocean-gateway` service is stateless - only Redis holds events after ingestion. **Redis must use persistent storage** so stream data survives a Redis pod crash or restart. * If you use the **bundled Redis** (deployed via the `ocean-gateway` Helm chart), persistence is enabled by default via a PVC. Do not disable it unless you accept event loss on Redis restarts. * If you **bring your own Redis**, you are responsible for ensuring durability - for example, a managed Redis service with persistence, or your own Redis deployment with a volume and RDB/AOF enabled. #### Live events base URL[​](#live-events-base-url "Direct link to Live events base URL") Each integration must have a unique, stable base URL, set via the `liveEvents.baseUrl` Helm value, which the integration pod exposes internally as the `OCEAN__BASE_URL` environment variable. This value is used as the Redis Stream key, so it must be unique across all integrations sharing the same Redis instance. The URL must follow this exact format: ``` https://live-events.your-domain.com/live-events/ ``` Avoid URL collisions Two integrations with the same `OCEAN__BASE_URL` on the same Redis instance will collide and corrupt each other's event stream. Use a different `` per integration, and avoid trailing slashes or variations between environments. #### Ingress[​](#ingress "Direct link to Ingress") Your Ingress must route incoming webhook traffic to `ocean-gateway`, not directly to the integration pod. If you manage your Ingress outside of the Helm chart, configure the routing rule manually to point to the `ocean-gateway` service. **Split routing for webhook handshakes**: some integrations perform a handshake during webhook setup using HTTP methods other than `POST` (such as `GET` or `OPTIONS`) to verify the endpoint before sending events. Since `ocean-gateway` handles only `POST` requests, handshake requests must be routed directly to the integration pod. Integrations that require this split routing include [**Azure DevOps**](/context-lake/ingestion/ingest-data-into-port/native-integrations/available-integrations/git/azure-devops/.md). See each integration's documentation for the exact configuration. ### Deployment steps[​](#deployment-steps "Direct link to Deployment steps") * Bundled Redis (default) * Bring your own Redis Bundled Redis is enabled by default in the `ocean-gateway` Helm chart. Use this path if you don't already run Redis. #### Step 1 - Deploy ocean-gateway Set a strong password, and optionally your Ingress values. You will reuse this same password in Step 2: ``` helm upgrade --install ocean-gateway port-labs/ocean-gateway \ --namespace ocean-gateway --create-namespace \ --set redis.auth.password= \ --set ingress.enabled=true \ --set ingress.className=nginx \ --set ingress.host=live-events.your-domain.com ``` #### Step 2 - Create a Redis credentials secret for the integration `ocean-gateway` auto-wires its own Redis connection, but the integration needs a separate secret with both sets of keys. Use the same `` you set in Step 1: ``` kubectl create secret generic live-events-redis-credentials \ --namespace \ --from-literal=REDIS_LIVE_EVENTS_URL=ocean-gateway-redis-master.ocean-gateway.svc.cluster.local:6379 \ --from-literal=REDIS_LIVE_EVENTS_PASSWORD= \ --from-literal=REDIS_LIVE_EVENTS_ENABLE_TLS=false \ --from-literal=OCEAN__LIVE_EVENTS__REDIS__URL=redis://ocean-gateway-redis-master.ocean-gateway.svc.cluster.local:6379 \ --from-literal=OCEAN__LIVE_EVENTS__REDIS__PASSWORD= \ --from-literal=OCEAN__LIVE_EVENTS__REDIS__ENABLE_TLS=false ``` Release name Replace `ocean-gateway` with your release name and namespace if they differ. #### Configure your Ingress Route incoming webhook `POST` traffic to `ocean-gateway`, not directly to the integration pod: ``` https://live-events.your-domain.com/live-events//integration/ ``` Handshake routing Some integrations (Azure DevOps, Okta) verify endpoints with `GET` or `OPTIONS`. `ocean-gateway` handles `POST` requests only, so route handshake requests directly to the integration pod. See [Ingress](#ingress). #### Configure the integration for live events Set the following in the integration's Helm values: ``` liveEvents: baseUrl: https://live-events.your-domain.com/live-events/ isRedisStreamConsumerEnabled: true redis: credentialsSecretName: live-events-redis-credentials ``` Or with `--set` flags: ``` helm upgrade --install my-integration port-labs/port-ocean \ --namespace port-ocean --create-namespace \ --set liveEvents.baseUrl=https://live-events.your-domain.com/live-events/ \ --set liveEvents.isRedisStreamConsumerEnabled=true \ --set liveEvents.redis.credentialsSecretName=live-events-redis-credentials ``` * `baseUrl` (sets `OCEAN__BASE_URL`) - the integration's unique base URL, following the required format. See [Live events base URL](#live-events-base-url). * `isRedisStreamConsumerEnabled` - without this, `ocean-gateway` will keep writing events to Redis, but the integration pod will not read them. * `redis.credentialsSecretName` - the secret created above, so the integration connects to the same Redis instance as `ocean-gateway`. Unique base URL `liveEvents.baseUrl` must be unique per integration on the same Redis instance. See [Live events base URL](#live-events-base-url). #### Verify Confirm the deployment is healthy and events are flowing: * `ocean-gateway` pods are ready. * The integration consumer has `OCEAN__LIVE_EVENTS__IS_REDIS_STREAM_CONSUMER_ENABLED=true`. * A test webhook returns `202 Accepted`. * Events appear in Port without a growing Redis backlog. ``` kubectl get pods -n ocean-gateway kubectl logs -n ocean-gateway deploy/ocean-gateway ``` Bring your own Redis if you already run Redis, or need full control over sizing, persistence, and TLS. #### Step 1 - Provision Redis Ensure a Redis 7.0+ instance is reachable from your cluster, with persistence enabled. See [Redis](#redis). Record the host, port, password, and whether TLS is required. #### Step 2 - Create a shared Redis credentials secret ``` kubectl create secret generic live-events-redis-credentials \ --namespace ocean-gateway \ --from-literal=REDIS_LIVE_EVENTS_URL=redis.example.svc.cluster.local:6379 \ --from-literal=REDIS_LIVE_EVENTS_PASSWORD= \ --from-literal=REDIS_LIVE_EVENTS_ENABLE_TLS=false \ --from-literal=OCEAN__LIVE_EVENTS__REDIS__URL=redis://redis.example.svc.cluster.local:6379 \ --from-literal=OCEAN__LIVE_EVENTS__REDIS__PASSWORD= \ --from-literal=OCEAN__LIVE_EVENTS__REDIS__ENABLE_TLS=false ``` With TLS (for example, a managed Redis service), use `rediss://` in `OCEAN__LIVE_EVENTS__REDIS__URL` and set both `_ENABLE_TLS` keys to `true`. Copy the secret to the integration namespace if it differs from the `ocean-gateway` namespace. #### Step 3 - Deploy ocean-gateway with your external Redis Disable the bundled Redis and connect to your instance, using either a shared secret or inline values. **Option 1 - Shared secret (recommended):** ``` helm upgrade --install ocean-gateway port-labs/ocean-gateway \ --namespace ocean-gateway --create-namespace \ --set redis.enabled=false \ --set redis.existingSecret=live-events-redis-credentials \ --set ingress.enabled=true \ --set ingress.className=nginx \ --set ingress.host=live-events.your-domain.com ``` **Option 2 - Inline URL and password:** ``` helm upgrade --install ocean-gateway port-labs/ocean-gateway \ --namespace ocean-gateway --create-namespace \ --set redis.enabled=false \ --set redis.url=redis.example.svc.cluster.local:6379 \ --set redis.password= \ --set ingress.enabled=true \ --set ingress.className=nginx \ --set ingress.host=live-events.your-domain.com ``` With TLS, also add `--set redis.tls.enabled=true`. #### Configure your Ingress Route incoming webhook `POST` traffic to `ocean-gateway`, not directly to the integration pod: ``` https://live-events.your-domain.com/live-events//integration/ ``` Handshake routing Some integrations (Azure DevOps, Okta) verify endpoints with `GET` or `OPTIONS`. `ocean-gateway` handles `POST` requests only, so route handshake requests directly to the integration pod. See [Ingress](#ingress). #### Configure the integration for live events Set the following in the integration's Helm values: ``` liveEvents: baseUrl: https://live-events.your-domain.com/live-events/ isRedisStreamConsumerEnabled: true redis: credentialsSecretName: live-events-redis-credentials ``` Or with `--set` flags: ``` helm upgrade --install my-integration port-labs/port-ocean \ --namespace port-ocean --create-namespace \ --set liveEvents.baseUrl=https://live-events.your-domain.com/live-events/ \ --set liveEvents.isRedisStreamConsumerEnabled=true \ --set liveEvents.redis.credentialsSecretName=live-events-redis-credentials ``` * `baseUrl` (sets `OCEAN__BASE_URL`) - the integration's unique base URL, following the required format. See [Live events base URL](#live-events-base-url). * `isRedisStreamConsumerEnabled` - without this, `ocean-gateway` will keep writing events to Redis, but the integration pod will not read them. * `redis.credentialsSecretName` - the secret created above, so the integration connects to the same Redis instance as `ocean-gateway`. Unique base URL `liveEvents.baseUrl` must be unique per integration on the same Redis instance. See [Live events base URL](#live-events-base-url). #### Verify Confirm the deployment is healthy and events are flowing: * `ocean-gateway` pods are ready. * The integration consumer has `OCEAN__LIVE_EVENTS__IS_REDIS_STREAM_CONSUMER_ENABLED=true`. * A test webhook returns `202 Accepted`. * Events appear in Port without a growing Redis backlog. ``` kubectl get pods -n ocean-gateway kubectl logs -n ocean-gateway deploy/ocean-gateway ``` ### Scaling live events consumers (optional)[​](#scaling-live-events-consumers-optional "Direct link to Scaling live events consumers (optional)") With this infrastructure, live event consumer pods can be deployed independently from the main integration pod. This is useful when: * You need to scale event processing horizontally under high event volumes. * You want to isolate live event processing from integration resync logic. To scale up consumers, set the replica count in your Helm chart values: ``` liveEvents: worker: enabled: true replicaCount: 3 ``` Each additional replica joins the same Redis consumer group and starts processing events in parallel. No other configuration changes are needed. Scalable mode For high-throughput environments, consider also enabling scalable mode for the integration itself to decouple the resync process from live event processing. This option is available in each integration's self-hosted installation section. ### Delivery and recovery[​](#delivery-and-recovery "Direct link to Delivery and recovery") Each consumer pod reads events from the Redis Stream as part of a consumer group, so every event is delivered to a single consumer. Once the consumer finishes handling an event, it acknowledges it in Redis, including when processing failed after all of its [retries](/context-lake/ingestion/ingest-data-into-port/native-integrations/ocean-framework/live-events-processing/.md#retries-and-timeouts). If a consumer pod stops while it is processing an event, the event stays pending in Redis. A background maintenance worker, running in every consumer pod, reclaims events that have been pending for too long and requeues them for another consumer. An event that is requeued too many times is discarded. The worker also removes idle consumers that have no pending events from the consumer group. Ocean detects Redis Cluster automatically, so no extra configuration is needed for clustered Redis. ### Consumer settings reference[​](#consumer-settings-reference "Direct link to Consumer settings reference") The following environment variables configure the integration's live events consumer. Only the connection settings are required, as shown in the [deployment steps](#deployment-steps). The other settings have defaults that fit most deployments. **Live events consumer settings (click to expand)** | Variable | Description | Default