Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
* Added ``client.github_app_installations`` resource with ``list`` (supports ``filter[name]`` and ``filter[installation_id]``) and ``read`` methods for looking up GitHub App installations the authenticated user can see. Returns ``GitHubAppInstallation`` records carrying both the HCP-side ``id`` (``ghain-...``) and the GitHub-side numeric ``installation_id``. The actual GitHub App authorisation flow happens through the HCP Terraform UI; this resource is the discovery surface workspace/stack/registry-module VCS configuration consumes.
* Added model ``GitHubAppInstallation``, ``GitHubAppInstallationListOptions``, ``GitHubAppInstallationType``.
* Added typed exception ``InvalidGitHubAppInstallationIDError``.

### HYOK OIDC Configurations
* Added aws_oidc_configurations, azure_oidc_configurations, gcp_oidc_configurations, and vault_oidc_configurations resources with create, read, update, and delete methods for Hold-Your-Own-Key OIDC configuration records. All four hit a single polymorphic HCP endpoint (POST /organizations/{org}/oidc-configurations for create, /oidc-configurations/{id} for read/update/delete) dispatched by JSON:API data.type, matching the structure used by go-tfe and the terraform-tfe provider.
* Added typed models per provider: AWSOIDCConfiguration / AzureOIDCConfiguration / GCPOIDCConfiguration / VaultOIDCConfiguration plus matching CreateOptions and UpdateOptions for each.
* Azure / GCP / Vault UpdateOptions are fully partial — only supplied fields are sent on the wire. AWSOIDCConfigurationUpdateOptions REQUIRES role_arn because the AWS resource has exactly one updatable attribute, matching go-tfe's AWSOIDCConfigurationUpdateOptions.valid() behaviour (ErrRequiredRoleARN). Constructing AWSOIDCConfigurationUpdateOptions() with no arguments now raises a pydantic ValidationError at construction time instead of silently sending an empty PATCH whose server-side behaviour was never verified.
* AWSOIDCConfigurationCreateOptions and AWSOIDCConfigurationUpdateOptions both reject empty-string role_arn values via a non-empty field validator, mirroring go-tfe's local validation.
* Added InvalidOIDCConfigurationIDError typed exception.
* These resources require HYOK / Premium entitlement on the organization; calls against a non-HYOK org return NotFound. The SDK manages only the HCP-side configuration record — the cloud-side trust resources (IAM role, Azure federated credential, GCP workload identity pool, Vault JWT auth method) still need to be provisioned separately.

## Bug Fixes

### Pagination
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ and upstream HCP Terraform API docs.
|---|---|
| Configure the SDK | [Authentication](./docs/authentication.md), [Pagination](./docs/pagination.md), [Logging](./docs/LOGGING.md) |
| API guides | [API index](./docs/api/index.md), [Workspaces](./docs/api/workspaces.md), [Runs/plans/applies](./docs/api/runs-plans-applies.md), [State versions](./docs/api/state-versions.md) |
| Scenario guides | [API-driven run](./docs/scenarios/api-driven-run.md), [State management](./docs/scenarios/state-management.md), [Migrate workspaces and state](./docs/scenarios/migrate-workspaces-and-state.md), [Team access onboarding](./docs/scenarios/team-access-onboarding.md), [No-code provisioning](./docs/scenarios/no-code-provisioning.md), [TFE identity bootstrap](./docs/scenarios/tfe-identity-bootstrap.md), [TFE admin bootstrap](./docs/scenarios/tfe-admin-bootstrap.md) |
| Scenario guides | [API-driven run](./docs/scenarios/api-driven-run.md), [State management](./docs/scenarios/state-management.md), [Migrate workspaces and state](./docs/scenarios/migrate-workspaces-and-state.md), [Team access onboarding](./docs/scenarios/team-access-onboarding.md), [No-code provisioning](./docs/scenarios/no-code-provisioning.md), [TFE identity bootstrap](./docs/scenarios/tfe-identity-bootstrap.md), [TFE admin bootstrap](./docs/scenarios/tfe-admin-bootstrap.md), [OIDC dynamic credentials](./docs/scenarios/oidc-dynamic-credentials.md) |
| Operations guides | [Troubleshooting](./docs/troubleshooting.md), [Errors](./docs/errors.md), [Terraform Enterprise](./docs/terraform-enterprise.md) |
| Contribute to the SDK | [CONTRIBUTING](./docs/CONTRIBUTING.md), [ITERATORS](./docs/ITERATORS.md), [MODELS](./docs/MODELS.md), [RESOURCE](./docs/RESOURCE.md) |

Expand Down
5 changes: 5 additions & 0 deletions docs/api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ column.
| `client.agent_tokens` | `AgentTokens` | `list`, `read`, `create`, `delete` | [agent.py](../../examples/agent.py) | [Agent tokens](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agent-tokens) |
| `client.registry_modules` | `RegistryModules` | `list`, `read`, `create`, `update`, `delete`, version and upload helpers | [registry_module.py](../../examples/registry_module.py) | [Registry modules](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/modules) |
| `client.no_code_modules` | `NoCodeModules` | `create`, `read`, `update`, `delete`, `read_variables`, `create_workspace`, `upgrade_workspace`, `read_workspace_upgrade`, `confirm_workspace_upgrade` | [no_code_provisioning.py](../../examples/no_code_provisioning.py) | [No-code provisioning](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/no-code-provisioning) |
| `client.aws_oidc_configurations` | `AWSOIDCConfigurations` | `create`, `read`, `update`, `delete` | [oidc_configurations.py](../../examples/oidc_configurations.py) | [AWS OIDC](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/aws) |
| `client.azure_oidc_configurations` | `AzureOIDCConfigurations` | `create`, `read`, `update`, `delete` | [oidc_configurations.py](../../examples/oidc_configurations.py) | [Azure OIDC](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/azure) |
| `client.gcp_oidc_configurations` | `GCPOIDCConfigurations` | `create`, `read`, `update`, `delete` | [oidc_configurations.py](../../examples/oidc_configurations.py) | [GCP OIDC](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/gcp) |
| `client.vault_oidc_configurations` | `VaultOIDCConfigurations` | `create`, `read`, `update`, `delete` | [oidc_configurations.py](../../examples/oidc_configurations.py) | [Vault OIDC](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/vault) |
| `client.registry_providers` | `RegistryProviders` | `list`, `read`, `create`, `delete` | [registry_provider.py](../../examples/registry_provider.py) | [Registry providers](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/providers) |
| `client.registry_provider_versions` | `RegistryProviderVersions` | `list`, `read`, `create`, `delete` | [registry_provider_version.py](../../examples/registry_provider_version.py) | [Registry providers](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/providers) |
| `client.registry_provider_platforms` | `RegistryProviderPlatforms` | `list`, `read`, `create`, `delete` | [registry_provider_platform.py](../../examples/registry_provider_platform.py) | [Registry providers](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/private-registry/providers) |
Expand Down Expand Up @@ -119,5 +123,6 @@ HCP Terraform (SaaS).
- [policies.md](policies.md)
- [run-tasks.md](run-tasks.md)
- [no-code-provisioning.md](no-code-provisioning.md)
- [oidc-configurations.md](oidc-configurations.md)
- [admin-identity.md](admin-identity.md)
- [organization-defaults-and-token-ttl.md](organization-defaults-and-token-ttl.md)
188 changes: 188 additions & 0 deletions docs/api/oidc-configurations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# HYOK OIDC configurations

Hold-Your-Own-Key (HYOK) OIDC configurations let HCP Terraform federate to
AWS, Azure, GCP, or Vault without storing a static credential. pyTFE exposes
one service per provider:

- `client.aws_oidc_configurations`
- `client.azure_oidc_configurations`
- `client.gcp_oidc_configurations`
- `client.vault_oidc_configurations`

Upstream docs:

- AWS: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/aws
- Azure: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/azure
- GCP: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/gcp
- Vault: https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/vault

Example: [oidc_configurations.py](../../examples/oidc_configurations.py)

## What these resources do (and do not) manage

These services manage the **HCP Terraform-side configuration record** only.
They do **not** provision the cloud-side trust resources:

| The SDK creates | You still need to provision separately |
|---|---|
| AWS OIDC configuration record (role ARN, organization) | IAM OIDC provider for HCP Terraform; IAM role and trust policy |
| Azure OIDC configuration record (client/subscription/tenant IDs) | Azure AD app registration; service principal; federated credential |
| GCP OIDC configuration record (SA email, project number, workload provider name) | Workload Identity Federation pool/provider; service account IAM bindings |
| Vault OIDC configuration record (address, role, auth path, namespace) | Vault JWT auth method; role; policies |

These configurations require **HYOK / Premium entitlement** on the
organization. Calls against a non-HYOK org return `404` or `403`.

For per-workspace dynamic credentials (not HYOK), see
[scenarios/oidc-dynamic-credentials.md](../scenarios/oidc-dynamic-credentials.md)
— that's still done via `client.variables` or `client.variable_sets`.

## Shared HTTP shape

All four providers hit the same endpoints; the provider is determined by the
JSON:API `data.type` string in the body, not the URL:

| Operation | Method | Path |
|---|---|---|
| Create | `POST` | `/api/v2/organizations/{org}/oidc-configurations` |
| Read | `GET` | `/api/v2/oidc-configurations/{id}` |
| Update | `PATCH` | `/api/v2/oidc-configurations/{id}` |
| Delete | `DELETE` | `/api/v2/oidc-configurations/{id}` |

`data.type` values per provider:

| Provider | `data.type` |
|---|---|
| AWS | `aws-oidc-configurations` |
| Azure | `azure-oidc-configurations` |
| GCP | `gcp-oidc-configurations` |
| Vault | `vault-oidc-configurations` |

## AWS

| Method | Purpose |
|---|---|
| `client.aws_oidc_configurations.create(organization, options)` | Register an IAM role ARN for OIDC federation. |
| `client.aws_oidc_configurations.read(oidc_configuration_id)` | Read configuration. |
| `client.aws_oidc_configurations.update(oidc_configuration_id, options)` | Update role ARN. |
| `client.aws_oidc_configurations.delete(oidc_configuration_id)` | Delete the configuration. |

```python
from pytfe import TFEClient
from pytfe.models import AWSOIDCConfigurationCreateOptions

client = TFEClient()

aws = client.aws_oidc_configurations.create(
"my-organization",
AWSOIDCConfigurationCreateOptions(
role_arn="arn:aws:iam::123456789012:role/hcp-terraform",
),
)
print(aws.id, aws.role_arn)
```

## Azure

| Method | Purpose |
|---|---|
| `client.azure_oidc_configurations.create(organization, options)` | Register Azure AD app/subscription/tenant. |
| `client.azure_oidc_configurations.read(oidc_configuration_id)` | Read configuration. |
| `client.azure_oidc_configurations.update(oidc_configuration_id, options)` | Update one or more IDs. |
| `client.azure_oidc_configurations.delete(oidc_configuration_id)` | Delete the configuration. |

```python
from pytfe.models import AzureOIDCConfigurationCreateOptions

azure = client.azure_oidc_configurations.create(
"my-organization",
AzureOIDCConfigurationCreateOptions(
client_id="00000000-0000-0000-0000-000000000000",
subscription_id="11111111-1111-1111-1111-111111111111",
tenant_id="22222222-2222-2222-2222-222222222222",
),
)
```

All three of `client_id`, `subscription_id`, `tenant_id` are required on
create. Update accepts any subset; unset fields are not touched.

## GCP

| Method | Purpose |
|---|---|
| `client.gcp_oidc_configurations.create(organization, options)` | Register the service account + workload provider. |
| `client.gcp_oidc_configurations.read(oidc_configuration_id)` | Read configuration. |
| `client.gcp_oidc_configurations.update(oidc_configuration_id, options)` | Update SA email, project number, or provider name. |
| `client.gcp_oidc_configurations.delete(oidc_configuration_id)` | Delete the configuration. |

```python
from pytfe.models import GCPOIDCConfigurationCreateOptions

gcp = client.gcp_oidc_configurations.create(
"my-organization",
GCPOIDCConfigurationCreateOptions(
service_account_email="tfc@my-project.iam.gserviceaccount.com",
project_number="123456789012",
workload_provider_name=(
"projects/123456789012/locations/global/"
"workloadIdentityPools/hcp/providers/hcp-terraform"
),
),
)
```

## Vault

Vault has the most non-obvious field mappings — the Python names differ from
the wire names:

| Python field | Wire name | Required on create |
|---|---|---|
| `address` | `address` | yes |
| `role_name` | `role` | yes |
| `namespace` | `namespace` | no |
| `jwt_auth_path` | `auth-path` | no |
| `tls_ca_certificate` | `encoded-cacert` | no |

| Method | Purpose |
|---|---|
| `client.vault_oidc_configurations.create(organization, options)` | Register Vault address + role. |
| `client.vault_oidc_configurations.read(oidc_configuration_id)` | Read configuration. |
| `client.vault_oidc_configurations.update(oidc_configuration_id, options)` | Update any field. |
| `client.vault_oidc_configurations.delete(oidc_configuration_id)` | Delete the configuration. |

```python
from pytfe.models import VaultOIDCConfigurationCreateOptions

vault = client.vault_oidc_configurations.create(
"my-organization",
VaultOIDCConfigurationCreateOptions(
address="https://vault.example.com",
role_name="hcp-terraform",
namespace="admin",
jwt_auth_path="jwt",
tls_ca_certificate="-----BEGIN CERTIFICATE-----\n...",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Encode the Vault CA certificate before sending it

This example passes a raw PEM block into the field that is serialized as encoded-cacert, but the Vault OIDC API expects that attribute to be a base64-encoded CA certificate. Users copying this for self-hosted Vault with a custom CA will send an invalid value and get a server-side validation error; the example should either base64-encode the PEM first or show an already encoded value.

Useful? React with 👍 / 👎.

),
)
```

## Token requirements

Write endpoints require an organization, owner, or HYOK admin token. See
HCP's HYOK docs for the exact permission model. Read endpoints follow the
same permission rules as other organization-scoped reads.

## Operational notes

- **Update is partial.** Pass only the fields you want to change. Unset
fields are not sent on the wire, so the server keeps the existing value.
- **Plan rotations carefully.** Updating the IAM role ARN or service account
email mid-flight will interrupt any in-progress runs that rely on the
federated credential.
- **Configurations are per-organization.** If you have multiple HCP
Terraform organizations sharing a cloud account, each needs its own
configuration record (and a distinct trust policy/federated credential on
the cloud side).
- **HYOK is required.** Without the entitlement these endpoints return
`404`. The SDK will surface that as `pytfe.errors.NotFound`.
Loading
Loading