Terraform Cloud & Terraform Enterprise
Port's Terraform Cloud integration allows you to model Terraform Cloud resources in your software catalog and ingest data into them.
Core support
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
Choose your preferred installation method below. Not sure which to pick? See the installation methods overview.
The prerequisites required for installation are listed below.
-
Go to the TerraformCloud Connectors page in Port.
-
Under
Select your installation method, chooseHosted by Port. -
Configure the
Installation parametersandAdvanced configurationas you wish (see below for details).
Installation parameters
Each integration requires specific parameters (such as an API token, a URL, etc.), as seen in Port's UI when installing it. Hover over the ⓘ icon next to each parameter to see more details about it.
Advanced configuration
- During the installation process each integration may have additional settings under the
Advanced configurationsection in Port's UI. Additionally, each integration has one or more settings that can be configured after installation. To do so, click on the integration's name in the Connectors page and navigate to theSettingtab. Hover over the ⓘ icon next to each setting to see more details about it.
Port secrets
Some integration settings require sensitive pieces of data, such as tokens. For these settings, Port secrets will be used, ensuring that your sensitive data is encrypted and secure.
When filling in such a setting, its value will be obscured (shown as ••••••••).
For each such setting, Port will automatically create a secret in your organization.
To see all secrets in your organization, follow these steps.
Port source IP addresses
When using this installation method, Port will make outbound calls to your 3rd-party applications from static IP addresses. You may need to add these addresses to your allowlist, in order to allow Port to interact with the integrated service:
- Europe (EU):
54.73.167.226,63.33.143.237,54.76.185.219 - United States (US):
3.234.37.33,54.225.172.136,3.225.234.99
-
Go to the Terraform-Cloud Connectors page in your portal.
-
Select the
Self-hostedmethod. -
A
helmcommand 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.
Required parameters:
port.clientId- Your Port client id.port.clientSecret- Your Port client secret.port.baseUrl- Your Port API URL -https://api.port.iofor EU,https://api.us.port.iofor US.integration.identifier- Change the identifier to describe your integration.integration.type- The integration type.integration.eventListener.type- The event listener type.integration.config.appHost- Your application's host url. Required for real-time updates via webhooks and when installing Terraform Enterprise (self hosted).integration.config.terraformCloudHost- Your Terraform host. For examplehttps://app.terraform.io.integration.config.terraformCloudToken- Terraform Cloud API token with read permissions for organizations, projects, workspaces, runs, and state versions. Provide any one of the following tokens: user API token (default read scopes), team API token (read access to those resources), or organization API token (equivalent read permissions). Docs can be found here.
Optional parameters (click to expand)
scheduledResyncInterval- The number of minutes between each resync.initializePortResources- When set to true the integration will create default blueprints and the port App config Mapping, defaults is true.sendRawDataExamples- Enable sending raw data examples from the third party API to port for testing and managing the integration mapping, default is true.
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 for details.
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
helm install my-integration port-labs/port-ocean \
# ... other parameters
--set resources.limits.cpu=800m \
--set resources.limits.memory=2Gi \
--set resources.requests.cpu=200m \
--set resources.requests.memory=2Gi
- GitHub
- Jenkins
- Azure DevOps
- GitLab
- Go to the TerraformCloud Connectors page in your portal.
- Under Select your installation method, choose your desired CI/CD platform.
- Copy the workflow and replace the placeholders with your values.
Below are the available parameters and their descriptions.
| Parameter | Description | Required? | Secret?Store this value as a GitHub Actions secret |
|---|---|---|---|
port_client_id | Your Port client ID (How to get the credentials). | ✓ | ✓ |
port_client_secret | Your Port client secret (How to get the credentials). | ✓ | ✓ |
port_base_url | Your Port API URL (https://api.port.io for EU, https://api.us.port.io for US). | ✓ | ✗ |
terraform_cloud_host | Your Terraform host. For example https://app.terraform.io. | ✓ | ✓ |
terraform_cloud_token | The Terraform Cloud API token. | ✓ | ✓ |
initialize_port_resources | Default true. When set to true, the integration will create default blueprints and the Port app config mapping. | ✗ | ✗ |
integration_identifier | The identifier of the integration that will be installed. | ✗ | ✗ |
send_raw_data_examples | Enable sending raw data examples from the third-party API to Port for testing and managing the integration mapping. Default is true. | ✗ | ✗ |
Your Jenkins agent must be able to run Docker commands.
Below are the available parameters and their descriptions.
| Parameter | Description | Required? | Secret?Store this value as a Jenkins credential (Secret Text) |
|---|---|---|---|
OCEAN__ | Your Port client ID (How to get the credentials). | ✓ | ✓ |
OCEAN__ | Your Port client secret (How to get the credentials). | ✓ | ✓ |
OCEAN__ | Your Port API URL (https://api.port.io for EU, https://api.us.port.io for US). | ✓ | ✗ |
OCEAN__ | Your Terraform host. For example https://app.terraform.io. | ✓ | ✓ |
OCEAN__ | The Terraform Cloud API token. | ✓ | ✓ |
OCEAN__ | Default true. When set to true, the integration will create default blueprints and the Port app config mapping. | ✗ | ✗ |
OCEAN__ | The identifier of the integration that will be installed. | ✗ | ✗ |
OCEAN__ | Enable sending raw data examples from the third-party API to Port for testing and managing the integration mapping. Default is true. | ✗ | ✗ |
Here is an example for a Jenkinsfile pipeline file:Jenkins pipeline (click to expand)
Prerequisites
- Your Azure Devops agent should be able to run docker commands. Learn more about agents here.
- You must have permissions to create and manage Azure DevOps variable groups for storing credentials.
Set up your credentials
- Create a Variable Group: Name it port-ocean-credentials.
- Store the required variables (see the table below).
- Authorize Your Pipeline:
-
Go to "Library" -> "Variable groups."
-
Find port-ocean-credentials and click on it.
-
Select "Pipeline Permissions" and add your pipeline to the authorized list.
-
Below are the available parameters and their descriptions.
| Parameter | Description | Required? | Secret?Store this value as an Azure DevOps pipeline variable |
|---|---|---|---|
OCEAN__ | Your Port client ID (How to get the credentials). | ✓ | ✓ |
OCEAN__ | Your Port client secret (How to get the credentials). | ✓ | ✓ |
OCEAN__ | Your Port API URL (https://api.port.io for EU, https://api.us.port.io for US). | ✓ | ✗ |
OCEAN__ | Your Terraform host. For example https://app.terraform.io. | ✓ | ✓ |
OCEAN__ | The Terraform Cloud API token. | ✓ | ✓ |
OCEAN__ | Default true. When set to true, the integration will create default blueprints and the Port app config mapping. | ✗ | ✗ |
OCEAN__ | The identifier of the integration that will be installed. | ✗ | ✗ |
OCEAN__ | Enable sending raw data examples from the third-party API to Port for testing and managing the integration mapping. Default is true. | ✗ | ✗ |
Here is an example for a azure-pipeline.yml pipeline file:Azure pipeline (click to expand)
- Go to the TerraformCloud Connectors page in your portal.
- Under Select your installation method, choose your desired CI/CD platform.
- Copy the workflow and replace the placeholders with your values.
Below are the available parameters and their descriptions.
| Parameter | Description | Required? | Secret?Store this value as a GitLab CI/CD variable |
|---|---|---|---|
OCEAN__ | Your Port client ID (How to get the credentials). | ✓ | ✓ |
OCEAN__ | Your Port client secret (How to get the credentials). | ✓ | ✓ |
OCEAN__ | Your Port API URL (https://api.port.io for EU, https://api.us.port.io for US). | ✓ | ✗ |
OCEAN__ | Your Terraform host. For example https://app.terraform.io. | ✓ | ✓ |
OCEAN__ | The Terraform Cloud API token. | ✓ | ✓ |
OCEAN__ | Default true. When set to true, the integration will create default blueprints and the Port app config mapping. | ✗ | ✗ |
OCEAN__ | The identifier of the integration that will be installed. | ✗ | ✗ |
OCEAN__ | Enable sending raw data examples from the third-party API to Port for testing and managing the integration mapping. Default is true. | ✗ | ✗ |
Configuration
Port integrations use a YAML mapping block to ingest data from the third-party api into Port.
The mapping makes use of the JQ JSON processor to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API.
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
- kind: team
selector:
query: 'true'
port:
entity:
mappings:
identifier: .id
title: .attributes.name
blueprint: '"terraformCloudTeam"'
properties:
visibility: .attributes.visibility
usersCount: .attributes."users-count"
description: .attributes.description
ssoTeamId: .attributes."sso-team-id"
organizationAccess: .attributes."organization-access"
relations:
organization: .relationships.organization.data.id
- kind: organization-membership
selector:
query: 'true'
port:
entity:
mappings:
identifier: .id
title: .attributes.email
blueprint: '"terraformCloudOrganizationMembership"'
properties:
email: .attributes.email
status: .attributes.status
createdAt: .attributes."created-at"
userId: .relationships.user.data.id
relations:
organization: .relationships.organization.data.id
teams: '[.relationships.teams.data[].id]'
- kind: team-workspace-access
selector:
query: 'true'
port:
entity:
mappings:
identifier: .id
title: '"\(.relationships.team.data.id) → \(.relationships.workspace.data.id)"'
blueprint: '"terraformCloudTeamWorkspaceAccess"'
properties:
access: .attributes.access
runs: .attributes.runs
variables: .attributes.variables
stateVersions: .attributes."state-versions"
sentinelMocks: .attributes."sentinel-mocks"
workspaceLocking: .attributes."workspace-locking"
runTasks: .attributes."run-tasks"
policyOverrides: .attributes."policy-overrides"
relations:
team: .relationships.team.data.id
workspace: .relationships.workspace.data.id
- kind: team-project-access
selector:
query: 'true'
port:
entity:
mappings:
identifier: .id
title: '"\(.relationships.team.data.id) → \(.relationships.project.data.id)"'
blueprint: '"terraformCloudTeamProjectAccess"'
properties:
access: .attributes.access
projectAccess: .attributes."project-access"
workspaceAccess: .attributes."workspace-access"
relations:
team: .relationships.team.data.id
project: .relationships.project.data.id
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.
The organization-membership kind lists human members only, because Terraform Cloud omits service account memberships from the memberships listing. On organizations without the teams entitlement, Terraform Cloud returns a reduced team payload that omits organization-access, so the organizationAccess property is not ingested. Organizations whose teams and memberships your token cannot read are skipped, so revoking that access removes their entities from your catalog on the next resync.
- 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"
{
"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"
}
}
{
"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"
}
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.
run:applyingrun:completedrun:createdrun:erroredrun:needs_attentionrun:planningassessment:driftedassessment:check_failureassessment:failed- Self-hosted
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 asliveEvents.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.
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 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 parameter has to be set, or a manual resync will need to be triggered through Port's UI.
To test webhooks or live event delivery to your local environment, expose your local pod or service to the internet using ngrok (e.g. ngrok http http://localhost:8000)
Monitoring and sync status
To learn more about how to monitor and check the sync status of your integration, see the relevant documentation.
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 in the examples page.
Relevant Guides
For relevant guides and examples, see the guides section.