Skip to content

Commit d3f9bcf

Browse files
Merge branch 'main' into feature/aayush-oauth
2 parents 5876c8c + 48bcf5d commit d3f9bcf

45 files changed

Lines changed: 6578 additions & 79 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,107 @@
1-
# python-tfe
1+
# HCP Terraform and Terraform Enterprise **Python** Client (pyTFE)
2+
3+
[![PyPI](https://img.shields.io/pypi/v/pytfe.svg)](https://pypi.org/project/pytfe/)
4+
[![Python Versions](https://img.shields.io/pypi/pyversions/pytfe.svg)](https://pypi.org/project/pytfe/)
5+
[![CI](https://github.com/hashicorp/python-tfe/actions/workflows/ci.yml/badge.svg)](https://github.com/hashicorp/python-tfe/actions/workflows/ci.yml)
6+
[![License](https://img.shields.io/github/license/hashicorp/python-tfe.svg)](./LICENSE)
7+
[![Issues](https://img.shields.io/github/issues/hashicorp/python-tfe.svg)](https://github.com/hashicorp/python-tfe/issues)
8+
9+
The official **Python** API client for [HCP Terraform and Terraform Enterprise](https://www.hashicorp.com/products/terraform).
10+
11+
This client targets the [HCP Terraform V2 API](https://developer.hashicorp.com/terraform/cloud-docs/api-docs).
12+
As Terraform Enterprise is the self-hosted distribution of HCP Terraform, this client supports both **HCP Terraform** and **Terraform Enterprise** use cases. In this repository and API, we refer to the platform generically as *Terraform Enterprise* unless a feature is explicitly called out as only supported in one or the other (rare).
13+
14+
## Version Information
15+
16+
We follow Semantic Versioning. During the initial alpha period we use `0.y.z`:
17+
- **Minor** (`0.y.z → 0.(y+1).z`): new, backwards-compatible features and enhancements.
18+
- **Patch** (`0.y.z → 0.y.(z+1)`): bug fixes and performance improvements.
19+
- Occasionally, a function signature change that fixes incorrect behavior may appear in a minor version.
20+
21+
## Example Usage
22+
23+
Construct a new **pyTFE** client, then use the resource services on the client to access different parts of the Terraform Enterprise API. The following example lists all organizations.
24+
25+
### (Recommended) Using explicit config
26+
27+
```python
28+
import os
29+
from pytfe import TFEClient, TFEConfig
30+
31+
config = TFEConfig(
32+
host="https://tfe.local",
33+
token="insert-your-token-here",
34+
retry_server_errors=True,
35+
timeout=30.0,
36+
user_agent="example-app/0.1 pytfe/0.1",
37+
)
38+
39+
client = TFEClient(config)
40+
41+
orgs = client.organizations.list()
42+
for org in orgs.items:
43+
print(org.name)
44+
```
45+
46+
### Using the default config with environment variables
47+
48+
The default configuration reads the `TFE_ADDRESS` and `TFE_TOKEN` environment variables.
49+
50+
1. `TFE_ADDRESS` — URL of an HCP Terraform or Terraform Enterprise instance. Example: `https://tfe.local`
51+
2. `TFE_TOKEN` — An [API token](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/api-tokens) for the HCP Terraform or Terraform Enterprise instance.
52+
53+
54+
Environment variables are used as a fallback when `host` or `token` are not provided explicitly:
55+
56+
#### Using the default configuration
57+
```python
58+
from pytfe import TFEClient, TFEConfig
59+
60+
# Equivalent to providing no values; falls back to env vars if set.
61+
client = TFEClient(TFEConfig())
62+
orgs = client.organizations.list()
63+
for org in orgs.items:
64+
print(org.name)
65+
```
66+
67+
#### When host or token is empty
68+
```python
69+
from pytfe import TFEClient, TFEConfig
70+
71+
config = TFEConfig(address="", token="")
72+
client = TFEClient(config)
73+
74+
orgs = client.organizations.list()
75+
for org in orgs.items:
76+
print(org.name)
77+
```
78+
79+
## Documentation
80+
81+
- API reference and guides (SDK): **coming soon**
82+
- Terraform Enterprise API: https://developer.hashicorp.com/terraform/enterprise/api-docs
83+
84+
## Examples
85+
86+
See the [`examples/`](./examples) directory for runnable snippets covering common workflows (workspaces, variables, configuration versions, runs/plans/applies, state, agents).
87+
88+
## Running tests
89+
90+
See [`TESTS.md`](./docs/TESTS.md). Typical flow:
91+
92+
```bash
93+
pip install -e .[dev]
94+
make test
95+
```
96+
97+
## Issues and Contributing
98+
99+
See [`CONTRIBUTING.md`](./docs/CONTRIBUTING.md). We welcome issues and pull requests.
100+
101+
## Releases
102+
103+
See [`RELEASES.md`](./docs/RELEASES.md).
104+
105+
## License
106+
107+
This project is licensed under the **MPL-2.0**. See [`LICENSE`](./LICENSE).

doc/.keep

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Contributing to pytfe

docs/RELEASES.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## Release Process
2+
3+
python-tfe can be released as often as required. Documentation updates and test fixes that only touch test files don't require a release or tag. You can just merge these changes into `main` once they have been approved.
4+
5+
### Preparing a release
6+
7+
Start by comparing the main branch with the last release in order to fully understand which changes are being released. Compare the last release tag with main. For each meaningful change, double check the following:
8+
9+
1. Is the change added to CHANGELOG.md?
10+
2. Does the public package API follow all endpoint conventions, such as naming, pointer usage, and options availability? Once these are released, they are permanent in the current major release version.
11+
3. Are new features generally available in the HCP Terraform API? Or is there another considered reason to release them?
12+
13+
Steps to prepare the changelog for a new release:
14+
15+
1. Replace `# Unreleased` with the version you are releasing.
16+
2. Ensure there is a line with `# Unreleased` at the top of the changelog for future changes. Ideally we don't ask authors to add this line; this will make it clear where they should add their changelog entry.
17+
3. Ensure that each existing changelog entry for the new release has the author(s) attributed and a pull request linked, i.e `- Some new feature/bugfix by @some-github-user (#3)[link-to-pull-request]`
18+
4. Open a pull request with these changes titled `vX.XX.XX Changelog`. Once approved and merged, you can go ahead and create the release.
19+
20+
### Creating a release
21+
22+
1. [Create a new release in GitHub](https://help.github.com/en/github/administering-a-repository/creating-releases) by clicking on "Releases" and then "Draft a new release"
23+
2. Set the `Tag version` to a new tag, using [Semantic Versioning](https://semver.org/) as a guideline.
24+
3. Set the `Target` as `main`.
25+
4. Set the `Release title` to the tag you created, `vX.Y.Z`
26+
5. Use the description section to describe why you're releasing and what changes you've made. You should include links to merged PRs. Use the following headers in the description of your release:
27+
- BREAKING CHANGES: Use this for any changes that aren't backwards compatible. Include details on how to handle these changes.
28+
- FEATURES: Use this for any large new features added,
29+
- ENHANCEMENTS: Use this for smaller new features added
30+
- BUG FIXES: Use this for any bugs that were fixed.
31+
- NOTES: Use this section if you need to include any additional notes on things like upgrading, upcoming deprecations, or any other information you might want to highlight.
32+
33+
Markdown example:
34+
35+
```markdown
36+
ENHANCEMENTS
37+
* Add description of new small feature by @some-github-user (#3)[link-to-pull-request]
38+
39+
BUG FIXES
40+
* Fix description of a bug by @some-github-user (#2)[link-to-pull-request]
41+
* Fix description of another bug by @some-github-user (#1)[link-to-pull-request]
42+
```
43+
44+
6. Don't attach any binaries. The zip and tar.gz assets are automatically created and attached after you publish your release.
45+
7. Click "Publish release" to save and publish your release.

docs/TESTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Running tests

examples/agent.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""Simple Individual Agent operations example with the TFE Python SDK.
2+
3+
This example demonstrates:
4+
1. Listing agents within agent pools
5+
2. Reading individual agent details
6+
3. Agent status monitoring
7+
4. Using the organization SDK client
8+
9+
Note: Individual agents are created by running the agent binary, not through the API.
10+
This example shows how to manage agents that have already connected to agent pools.
11+
12+
Make sure to set the following environment variables:
13+
- TFE_TOKEN: Your Terraform Cloud/Enterprise API token
14+
- TFE_ADDRESS: Your Terraform Cloud/Enterprise URL (optional, defaults to https://app.terraform.io)
15+
- TFE_ORG: Your organization name
16+
17+
Usage:
18+
export TFE_TOKEN="your-token-here"
19+
export TFE_ORG="your-organization"
20+
python examples/agent_simple.py
21+
"""
22+
23+
import os
24+
25+
from tfe.client import TFEClient
26+
from tfe.config import TFEConfig
27+
from tfe.errors import NotFound
28+
from tfe.models.agent import AgentListOptions
29+
30+
31+
def main():
32+
"""Main function demonstrating agent operations."""
33+
# Get environment variables
34+
token = os.environ.get("TFE_TOKEN")
35+
org = os.environ.get("TFE_ORG")
36+
address = os.environ.get("TFE_ADDRESS", "https://app.terraform.io")
37+
38+
if not token:
39+
print("❌ TFE_TOKEN environment variable is required")
40+
return 1
41+
42+
if not org:
43+
print("❌ TFE_ORG environment variable is required")
44+
return 1
45+
46+
# Create TFE client
47+
config = TFEConfig(token=token, address=address)
48+
client = TFEClient(config=config)
49+
50+
print(f"🔗 Connected to: {address}")
51+
print(f"🏢 Organization: {org}")
52+
53+
try:
54+
# Example 1: Find agent pools to demonstrate agent operations
55+
print("\n📋 Finding agent pools...")
56+
agent_pools = client.agent_pools.list(org)
57+
58+
# Convert to list to check if empty and get count
59+
pool_list = list(agent_pools)
60+
if not pool_list:
61+
print("⚠️ No agent pools found. Create an agent pool first.")
62+
return 1
63+
64+
print(f"Found {len(pool_list)} agent pools:")
65+
for pool in pool_list:
66+
print(f" - {pool.name} (ID: {pool.id}, Agents: {pool.agent_count})")
67+
68+
# Example 2: List agents in each pool
69+
print("\n🤖 Listing agents in each pool...")
70+
total_agents = 0
71+
72+
for pool in pool_list:
73+
print(f"\n📂 Agents in pool '{pool.name}':")
74+
75+
# Use optional parameters for listing
76+
list_options = AgentListOptions(page_size=10) # Optional parameter
77+
agents = client.agents.list(pool.id, options=list_options)
78+
79+
# Convert to list to check if empty and iterate
80+
agent_list = list(agents)
81+
if agent_list:
82+
total_agents += len(agent_list)
83+
for agent in agent_list:
84+
print(f" - Agent {agent.id}")
85+
print(f" Name: {agent.name or 'Unnamed'}")
86+
print(f" Status: {agent.status}")
87+
print(f" Version: {agent.version or 'Unknown'}")
88+
print(f" IP: {agent.ip_address or 'Unknown'}")
89+
print(f" Last Ping: {agent.last_ping_at or 'Never'}")
90+
91+
# Example 3: Read detailed agent information
92+
try:
93+
agent_details = client.agents.read(agent.id)
94+
print(" ✅ Agent details retrieved successfully")
95+
print(f" Full name: {agent_details.name or 'Unnamed'}")
96+
print(f" Current status: {agent_details.status}")
97+
except NotFound:
98+
print(" ⚠️ Agent details not accessible")
99+
except Exception as e:
100+
print(f" ❌ Error reading agent details: {e}")
101+
102+
print("")
103+
else:
104+
print(" No agents found in this pool")
105+
106+
if total_agents == 0:
107+
print("\n⚠️ No agents found in any pools.")
108+
print("To see agents in action:")
109+
print("1. Create an agent pool")
110+
print("2. Run a Terraform Enterprise agent binary connected to the pool")
111+
print("3. Run this example again")
112+
else:
113+
print(f"\n📊 Total agents found across all pools: {total_agents}")
114+
115+
print("\n🎉 Agent operations completed successfully!")
116+
return 0
117+
118+
except NotFound as e:
119+
print(f"❌ Resource not found: {e}")
120+
return 1
121+
except Exception as e:
122+
print(f"❌ Error: {e}")
123+
return 1
124+
125+
126+
if __name__ == "__main__":
127+
exit(main())

0 commit comments

Comments
 (0)