Skip to content

Commit 26ff34e

Browse files
authored
Merge pull request #145 from hashicorp/feature/registry-provider-platform
Feature/registry provider platform
2 parents f6bf013 + 99c6e7a commit 26ff34e

12 files changed

Lines changed: 1050 additions & 123 deletions
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# Copyright IBM Corp. 2025, 2026
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import os
8+
9+
from pytfe import TFEClient, TFEConfig
10+
from pytfe.models import (
11+
RegistryProviderPlatformCreateOptions,
12+
RegistryProviderPlatformID,
13+
RegistryProviderPlatformListOptions,
14+
RegistryProviderVersionID,
15+
)
16+
17+
18+
def _print_header(title: str):
19+
print("\n" + "=" * 80)
20+
print(title)
21+
print("=" * 80)
22+
23+
24+
def main():
25+
parser = argparse.ArgumentParser(
26+
description="Registry Provider Platforms demo for python-tfe SDK"
27+
)
28+
parser.add_argument(
29+
"--address", default=os.getenv("TFE_ADDRESS", "https://app.terraform.io")
30+
)
31+
parser.add_argument("--token", default=os.getenv("TFE_TOKEN", ""))
32+
parser.add_argument("--organization", required=True, help="Organization name")
33+
parser.add_argument(
34+
"--registry-name",
35+
default="private",
36+
help="Registry name (default: private)",
37+
)
38+
parser.add_argument("--namespace", required=True, help="Provider namespace")
39+
parser.add_argument("--name", required=True, help="Provider name")
40+
parser.add_argument(
41+
"--version", required=True, help="Provider version (e.g., 1.0.0)"
42+
)
43+
parser.add_argument(
44+
"--page-size",
45+
type=int,
46+
default=100,
47+
help="Page size for listing platforms",
48+
)
49+
parser.add_argument("--create", action="store_true", help="Create a platform")
50+
parser.add_argument("--read", action="store_true", help="Read a specific platform")
51+
parser.add_argument(
52+
"--delete", action="store_true", help="Delete a specific platform"
53+
)
54+
parser.add_argument(
55+
"--os", dest="os", help="Operating system (e.g., linux, darwin)"
56+
)
57+
parser.add_argument("--arch", help="Architecture (e.g., amd64, arm64)")
58+
parser.add_argument("--shasum", help="SHA256 checksum of the provider binary")
59+
parser.add_argument("--filename", help="Filename of the provider binary zip")
60+
args = parser.parse_args()
61+
62+
cfg = TFEConfig(address=args.address, token=args.token)
63+
client = TFEClient(cfg)
64+
65+
version_id = RegistryProviderVersionID(
66+
organization_name=args.organization,
67+
registry_name=args.registry_name,
68+
namespace=args.namespace,
69+
name=args.name,
70+
version=args.version,
71+
)
72+
73+
# 1) List all platforms for the provider version
74+
_print_header(
75+
f"Listing platforms for {args.registry_name}/{args.namespace}/{args.name} @ {args.version}"
76+
)
77+
78+
list_options = RegistryProviderPlatformListOptions(page_size=args.page_size)
79+
80+
platform_count = 0
81+
for platform in client.registry_provider_platforms.list(
82+
version_id=version_id,
83+
options=list_options,
84+
):
85+
platform_count += 1
86+
print(f"- Platform {platform.os}/{platform.arch} (ID: {platform.id})")
87+
print(f" Filename: {platform.filename}")
88+
print(f" Shasum: {platform.shasum}")
89+
print(f" Provider Binary Uploaded: {platform.provider_binary_uploaded}")
90+
if platform.permissions:
91+
print(" Permissions:")
92+
print(f" Can Delete: {platform.permissions.can_delete}")
93+
print(f" Can Upload Asset: {platform.permissions.can_upload_asset}")
94+
if platform.links:
95+
print(" Links:")
96+
for key, value in platform.links.items():
97+
print(f" {key}: {value}")
98+
print()
99+
100+
if platform_count == 0:
101+
print("No platforms found.")
102+
else:
103+
print(f"Total: {platform_count} platforms")
104+
105+
# 2) Create a new platform (if --create flag is provided)
106+
if args.create:
107+
if not args.os:
108+
print("Error: --os is required for create operation")
109+
return
110+
if not args.arch:
111+
print("Error: --arch is required for create operation")
112+
return
113+
if not args.shasum:
114+
print("Error: --shasum is required for create operation")
115+
return
116+
if not args.filename:
117+
print("Error: --filename is required for create operation")
118+
return
119+
120+
_print_header(f"Creating platform: {args.os}/{args.arch}")
121+
122+
create_options = RegistryProviderPlatformCreateOptions(
123+
os=args.os,
124+
arch=args.arch,
125+
shasum=args.shasum,
126+
filename=args.filename,
127+
)
128+
129+
new_platform = client.registry_provider_platforms.create(
130+
version_id=version_id,
131+
options=create_options,
132+
)
133+
134+
print(f"Created platform: {new_platform.id}")
135+
print(f" OS: {new_platform.os}")
136+
print(f" Arch: {new_platform.arch}")
137+
print(f" Filename: {new_platform.filename}")
138+
print(f" Shasum: {new_platform.shasum}")
139+
print(f" Provider Binary Uploaded: {new_platform.provider_binary_uploaded}")
140+
141+
if new_platform.links:
142+
print("\n Upload URLs:")
143+
if "provider-binary-upload" in new_platform.links:
144+
print(
145+
f" Provider Binary: {new_platform.links['provider-binary-upload']}"
146+
)
147+
148+
# 3) Read a specific platform (if --read flag is provided)
149+
if args.read:
150+
if not args.os:
151+
print("Error: --os is required for read operation")
152+
return
153+
if not args.arch:
154+
print("Error: --arch is required for read operation")
155+
return
156+
157+
_print_header(f"Reading platform: {args.os}/{args.arch}")
158+
159+
platform_id = RegistryProviderPlatformID(
160+
organization_name=args.organization,
161+
registry_name=args.registry_name,
162+
namespace=args.namespace,
163+
name=args.name,
164+
version=args.version,
165+
os=args.os,
166+
arch=args.arch,
167+
)
168+
169+
platform = client.registry_provider_platforms.read(platform_id)
170+
171+
print(f"Platform ID: {platform.id}")
172+
print(f" OS: {platform.os}")
173+
print(f" Arch: {platform.arch}")
174+
print(f" Filename: {platform.filename}")
175+
print(f" Shasum: {platform.shasum}")
176+
print(f" Provider Binary Uploaded: {platform.provider_binary_uploaded}")
177+
178+
if platform.permissions:
179+
print(" Permissions:")
180+
print(f" Can Delete: {platform.permissions.can_delete}")
181+
print(f" Can Upload Asset: {platform.permissions.can_upload_asset}")
182+
183+
if platform.links:
184+
print(" Links:")
185+
for key, value in platform.links.items():
186+
print(f" {key}: {value}")
187+
188+
# 4) Delete a platform (if --delete flag is provided)
189+
if args.delete:
190+
if not args.os:
191+
print("Error: --os is required for delete operation")
192+
return
193+
if not args.arch:
194+
print("Error: --arch is required for delete operation")
195+
return
196+
197+
_print_header(f"Deleting platform: {args.os}/{args.arch}")
198+
199+
platform_id = RegistryProviderPlatformID(
200+
organization_name=args.organization,
201+
registry_name=args.registry_name,
202+
namespace=args.namespace,
203+
name=args.name,
204+
version=args.version,
205+
os=args.os,
206+
arch=args.arch,
207+
)
208+
209+
try:
210+
platform_to_delete = client.registry_provider_platforms.read(platform_id)
211+
print("Platform to delete:")
212+
print(f" ID: {platform_to_delete.id}")
213+
print(f" OS/Arch: {platform_to_delete.os}/{platform_to_delete.arch}")
214+
print(f" Filename: {platform_to_delete.filename}")
215+
except Exception as e:
216+
print(f"Error reading platform: {e}")
217+
return
218+
219+
client.registry_provider_platforms.delete(platform_id)
220+
print(f"\n Successfully deleted platform: {args.os}/{args.arch}")
221+
222+
# List remaining platforms
223+
_print_header("Listing platforms after deletion")
224+
remaining_count = 0
225+
for platform in client.registry_provider_platforms.list(version_id=version_id):
226+
remaining_count += 1
227+
print(f"- {platform.os}/{platform.arch} (ID: {platform.id})")
228+
229+
if remaining_count == 0:
230+
print("No platforms remaining.")
231+
else:
232+
print(f"Total remaining: {remaining_count} platforms")
233+
234+
235+
if __name__ == "__main__":
236+
main()

src/pytfe/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from .resources.query_run import QueryRuns
2828
from .resources.registry_module import RegistryModules
2929
from .resources.registry_provider import RegistryProviders
30+
from .resources.registry_provider_platform import RegistryProviderPlatforms
3031
from .resources.registry_provider_version import RegistryProviderVersions
3132
from .resources.reserved_tag_key import ReservedTagKeys
3233
from .resources.run import Runs
@@ -85,6 +86,7 @@ def __init__(self, config: TFEConfig | None = None):
8586
self.registry_modules = RegistryModules(self._transport)
8687
self.registry_providers = RegistryProviders(self._transport)
8788
self.registry_provider_versions = RegistryProviderVersions(self._transport)
89+
self.registry_provider_platforms = RegistryProviderPlatforms(self._transport)
8890

8991
# State and execution resources
9092
self.state_versions = StateVersions(self._transport)

src/pytfe/errors.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,63 @@ class InvalidTeamProjectAccessIDError(InvalidValues):
567567

568568
def __init__(self, message: str = "invalid value for team project access ID"):
569569
super().__init__(message)
570+
571+
572+
# Registry Provider Platform errors
573+
class RequiredOSError(RequiredFieldMissing):
574+
"""Raised when a required OS field is missing."""
575+
576+
def __init__(self, message: str = "os is required"):
577+
super().__init__(message)
578+
579+
580+
class RequiredArchError(RequiredFieldMissing):
581+
"""Raised when a required architecture field is missing."""
582+
583+
def __init__(self, message: str = "arch is required"):
584+
super().__init__(message)
585+
586+
587+
class RequiredShasumError(RequiredFieldMissing):
588+
"""Raised when a required shasum field is missing."""
589+
590+
def __init__(self, message: str = "shasum is required"):
591+
super().__init__(message)
592+
593+
594+
class RequiredFilenameError(RequiredFieldMissing):
595+
"""Raised when a required filename field is missing."""
596+
597+
def __init__(self, message: str = "filename is required"):
598+
super().__init__(message)
599+
600+
601+
class InvalidOSError(InvalidValues):
602+
"""Raised when an invalid OS field is provided."""
603+
604+
def __init__(self, message: str = "invalid value for os"):
605+
super().__init__(message)
606+
607+
608+
class InvalidArchError(InvalidValues):
609+
"""Raised when an invalid architecture field is provided."""
610+
611+
def __init__(self, message: str = "invalid value for arch"):
612+
super().__init__(message)
613+
614+
615+
class InvalidNamespaceError(InvalidValues):
616+
"""Raised when an invalid namespace field is provided."""
617+
618+
def __init__(self, message: str = "invalid value for namespace"):
619+
super().__init__(message)
620+
621+
622+
class InvalidRegistryNameError(InvalidValues):
623+
"""Raised when an invalid registry name field is provided."""
624+
625+
def __init__(
626+
self,
627+
message: str = "invalid value for registry-name. It must be either private or public",
628+
):
629+
super().__init__(message)

src/pytfe/models/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@
230230
RegistryProviderPermissions,
231231
RegistryProviderReadOptions,
232232
)
233+
from .registry_provider_platform import (
234+
RegistryProviderPlatform,
235+
RegistryProviderPlatformCreateOptions,
236+
RegistryProviderPlatformID,
237+
RegistryProviderPlatformListOptions,
238+
RegistryProviderPlatformPermissions,
239+
)
233240
from .registry_provider_version import (
234241
RegistryProviderVersion,
235242
RegistryProviderVersionCreateOptions,
@@ -500,6 +507,12 @@
500507
"RegistryProviderVersionID",
501508
"RegistryProviderVersionListOptions",
502509
"RegistryProviderVersionPermissions",
510+
# Registry provider platforms
511+
"RegistryProviderPlatform",
512+
"RegistryProviderPlatformCreateOptions",
513+
"RegistryProviderPlatformID",
514+
"RegistryProviderPlatformListOptions",
515+
"RegistryProviderPlatformPermissions",
503516
# Query runs
504517
"QueryRun",
505518
"QueryRunActions",
@@ -706,3 +719,6 @@
706719

707720
# Rebuild models with forward references after all models are loaded
708721
PolicyCheck.model_rebuild()
722+
RegistryProvider.model_rebuild()
723+
RegistryProviderVersion.model_rebuild()
724+
RegistryProviderPlatform.model_rebuild()

0 commit comments

Comments
 (0)