pragmatiks-gcp-provider 0.84.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.3
2
+ Name: pragmatiks-gcp-provider
3
+ Version: 0.84.0
4
+ Summary: GCP provider for Pragmatiks
5
+ Author: Pragmatiks
6
+ Author-email: Pragmatiks <team@pragmatiks.io>
7
+ Requires-Dist: pragmatiks-sdk>=0.17.1
8
+ Requires-Dist: google-cloud-container>=2.50.0
9
+ Requires-Dist: google-cloud-logging>=3.10.0
10
+ Requires-Dist: google-cloud-secret-manager>=2.20.0
11
+ Requires-Dist: google-api-python-client>=2.150.0
12
+ Requires-Dist: google-auth>=2.35.0
13
+ Requires-Python: >=3.13
14
+ Description-Content-Type: text/markdown
15
+
16
+ # GCP Provider
17
+
18
+ GCP provider for Pragmatiks - manage Google Cloud resources declaratively.
19
+
20
+ ## Available Resources
21
+
22
+ ### Secret (gcp/secret)
23
+
24
+ Manages secrets in GCP Secret Manager using user-provided service account credentials.
25
+
26
+ ```python
27
+ from gcp_provider import Secret, SecretConfig
28
+
29
+ # Define a secret
30
+ secret = Secret(
31
+ name="my-api-key",
32
+ config=SecretConfig(
33
+ project_id="my-gcp-project",
34
+ secret_id="api-key",
35
+ data="super-secret-value",
36
+ credentials={"type": "service_account", ...}, # or JSON string
37
+ ),
38
+ )
39
+ ```
40
+
41
+ **Config:**
42
+ - `project_id` - GCP project ID where the secret will be created
43
+ - `secret_id` - Identifier for the secret (must be unique per project)
44
+ - `data` - Secret payload data to store
45
+ - `credentials` - GCP service account credentials (JSON object or string)
46
+
47
+ **Outputs:**
48
+ - `resource_name` - Full GCP resource name (`projects/{project}/secrets/{id}`)
49
+ - `version_name` - Full version resource name including version number
50
+ - `version_id` - The version number as a string
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install pragmatiks-gcp-provider
56
+ ```
57
+
58
+ ## Development
59
+
60
+ ### Testing
61
+
62
+ ```bash
63
+ # Install dependencies
64
+ uv sync --dev
65
+
66
+ # Run tests
67
+ uv run pytest tests/
68
+ ```
69
+
70
+ ### Writing Tests
71
+
72
+ Use `ProviderHarness` to test lifecycle methods:
73
+
74
+ ```python
75
+ from pragma_sdk.provider import ProviderHarness
76
+ from gcp_provider import Secret, SecretConfig
77
+
78
+ async def test_create_secret():
79
+ harness = ProviderHarness()
80
+ result = await harness.invoke_create(
81
+ Secret,
82
+ name="test-secret",
83
+ config=SecretConfig(
84
+ project_id="test-project",
85
+ secret_id="my-secret",
86
+ data="secret-value",
87
+ credentials=mock_credentials,
88
+ ),
89
+ )
90
+ assert result.success
91
+ assert result.outputs.resource_name is not None
92
+ ```
93
+
94
+ ## Deployment
95
+
96
+ Push your provider to Pragmatiks platform:
97
+
98
+ ```bash
99
+ pragma provider push
100
+ ```
@@ -0,0 +1,85 @@
1
+ # GCP Provider
2
+
3
+ GCP provider for Pragmatiks - manage Google Cloud resources declaratively.
4
+
5
+ ## Available Resources
6
+
7
+ ### Secret (gcp/secret)
8
+
9
+ Manages secrets in GCP Secret Manager using user-provided service account credentials.
10
+
11
+ ```python
12
+ from gcp_provider import Secret, SecretConfig
13
+
14
+ # Define a secret
15
+ secret = Secret(
16
+ name="my-api-key",
17
+ config=SecretConfig(
18
+ project_id="my-gcp-project",
19
+ secret_id="api-key",
20
+ data="super-secret-value",
21
+ credentials={"type": "service_account", ...}, # or JSON string
22
+ ),
23
+ )
24
+ ```
25
+
26
+ **Config:**
27
+ - `project_id` - GCP project ID where the secret will be created
28
+ - `secret_id` - Identifier for the secret (must be unique per project)
29
+ - `data` - Secret payload data to store
30
+ - `credentials` - GCP service account credentials (JSON object or string)
31
+
32
+ **Outputs:**
33
+ - `resource_name` - Full GCP resource name (`projects/{project}/secrets/{id}`)
34
+ - `version_name` - Full version resource name including version number
35
+ - `version_id` - The version number as a string
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install pragmatiks-gcp-provider
41
+ ```
42
+
43
+ ## Development
44
+
45
+ ### Testing
46
+
47
+ ```bash
48
+ # Install dependencies
49
+ uv sync --dev
50
+
51
+ # Run tests
52
+ uv run pytest tests/
53
+ ```
54
+
55
+ ### Writing Tests
56
+
57
+ Use `ProviderHarness` to test lifecycle methods:
58
+
59
+ ```python
60
+ from pragma_sdk.provider import ProviderHarness
61
+ from gcp_provider import Secret, SecretConfig
62
+
63
+ async def test_create_secret():
64
+ harness = ProviderHarness()
65
+ result = await harness.invoke_create(
66
+ Secret,
67
+ name="test-secret",
68
+ config=SecretConfig(
69
+ project_id="test-project",
70
+ secret_id="my-secret",
71
+ data="secret-value",
72
+ credentials=mock_credentials,
73
+ ),
74
+ )
75
+ assert result.success
76
+ assert result.outputs.resource_name is not None
77
+ ```
78
+
79
+ ## Deployment
80
+
81
+ Push your provider to Pragmatiks platform:
82
+
83
+ ```bash
84
+ pragma provider push
85
+ ```
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "pragmatiks-gcp-provider"
3
+ version = "0.84.0"
4
+ description = "GCP provider for Pragmatiks"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "pragmatiks-sdk>=0.17.1",
9
+ "google-cloud-container>=2.50.0",
10
+ "google-cloud-logging>=3.10.0",
11
+ "google-cloud-secret-manager>=2.20.0",
12
+ "google-api-python-client>=2.150.0",
13
+ "google-auth>=2.35.0",
14
+ ]
15
+
16
+ authors = [
17
+ { name = "Pragmatiks", email = "team@pragmatiks.io" },
18
+ ]
19
+
20
+ [dependency-groups]
21
+ dev = [
22
+ "pytest>=8.4.1",
23
+ "pytest-asyncio>=1.0.0",
24
+ "pytest-cov>=7.0.0",
25
+ "pytest-mock>=3.14.0",
26
+ "ty>=0.0.14",
27
+ ]
28
+
29
+ [tool.pragma]
30
+ # Provider metadata - used by the platform for discovery and deployment
31
+ provider = "gcp"
32
+ package = "gcp_provider"
33
+
34
+ [tool.pytest.ini_options]
35
+ asyncio_mode = "auto"
36
+ asyncio_default_fixture_loop_scope = "function"
37
+
38
+ [tool.commitizen]
39
+ name = "cz_conventional_commits"
40
+ version = "0.84.0"
41
+ version_files = ["pyproject.toml:^version"]
42
+ tag_format = "gcp-v$version"
43
+ update_changelog_on_bump = true
44
+ changelog_file = "CHANGELOG.md"
45
+
46
+ [tool.ruff]
47
+ extend = "../../pyproject.toml"
48
+ line-length = 120
49
+
50
+ [tool.uv.build-backend]
51
+ module-name = "gcp_provider"
52
+
53
+ [build-system]
54
+ requires = ["uv_build>=0.9.8,<0.10.0"]
55
+ build-backend = "uv_build"
@@ -0,0 +1,53 @@
1
+ """GCP provider for Pragmatiks.
2
+
3
+ Provides GCP resources for managing infrastructure in Google Cloud Platform
4
+ using user-provided credentials (multi-tenant SaaS pattern).
5
+ """
6
+
7
+ from pragma_sdk import Provider
8
+
9
+ from gcp_provider.resources import (
10
+ GKE,
11
+ Database,
12
+ DatabaseConfig,
13
+ DatabaseInstance,
14
+ DatabaseInstanceConfig,
15
+ DatabaseInstanceOutputs,
16
+ DatabaseOutputs,
17
+ GKEConfig,
18
+ GKEOutputs,
19
+ Secret,
20
+ SecretConfig,
21
+ SecretOutputs,
22
+ User,
23
+ UserConfig,
24
+ UserOutputs,
25
+ )
26
+
27
+
28
+ gcp = Provider(name="gcp")
29
+
30
+ gcp.resource("cloudsql/database_instance")(DatabaseInstance)
31
+ gcp.resource("cloudsql/database")(Database)
32
+ gcp.resource("cloudsql/user")(User)
33
+ gcp.resource("gke")(GKE)
34
+ gcp.resource("secret")(Secret)
35
+
36
+ __all__ = [
37
+ "gcp",
38
+ "Database",
39
+ "DatabaseConfig",
40
+ "DatabaseInstance",
41
+ "DatabaseInstanceConfig",
42
+ "DatabaseInstanceOutputs",
43
+ "DatabaseOutputs",
44
+ "GKE",
45
+ "GKEConfig",
46
+ "GKEOutputs",
47
+ "Secret",
48
+ "SecretConfig",
49
+ "SecretOutputs",
50
+ "User",
51
+ "UserConfig",
52
+ "UserOutputs",
53
+ ]
@@ -0,0 +1,45 @@
1
+ """Resource definitions for gcp provider.
2
+
3
+ Import and export your Resource classes here for discovery by the runtime.
4
+ """
5
+
6
+ from gcp_provider.resources.cloudsql import (
7
+ Database,
8
+ DatabaseConfig,
9
+ DatabaseInstance,
10
+ DatabaseInstanceConfig,
11
+ DatabaseInstanceOutputs,
12
+ DatabaseOutputs,
13
+ User,
14
+ UserConfig,
15
+ UserOutputs,
16
+ )
17
+ from gcp_provider.resources.gke import (
18
+ GKE,
19
+ GKEConfig,
20
+ GKEOutputs,
21
+ )
22
+ from gcp_provider.resources.secret import (
23
+ Secret,
24
+ SecretConfig,
25
+ SecretOutputs,
26
+ )
27
+
28
+
29
+ __all__ = [
30
+ "Database",
31
+ "DatabaseConfig",
32
+ "DatabaseInstance",
33
+ "DatabaseInstanceConfig",
34
+ "DatabaseInstanceOutputs",
35
+ "DatabaseOutputs",
36
+ "GKE",
37
+ "GKEConfig",
38
+ "GKEOutputs",
39
+ "Secret",
40
+ "SecretConfig",
41
+ "SecretOutputs",
42
+ "User",
43
+ "UserConfig",
44
+ "UserOutputs",
45
+ ]
@@ -0,0 +1,32 @@
1
+ """GCP Cloud SQL resources for database instances, databases, and users."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from gcp_provider.resources.cloudsql.database import (
6
+ Database,
7
+ DatabaseConfig,
8
+ DatabaseOutputs,
9
+ )
10
+ from gcp_provider.resources.cloudsql.database_instance import (
11
+ DatabaseInstance,
12
+ DatabaseInstanceConfig,
13
+ DatabaseInstanceOutputs,
14
+ )
15
+ from gcp_provider.resources.cloudsql.user import (
16
+ User,
17
+ UserConfig,
18
+ UserOutputs,
19
+ )
20
+
21
+
22
+ __all__ = [
23
+ "Database",
24
+ "DatabaseConfig",
25
+ "DatabaseInstance",
26
+ "DatabaseInstanceConfig",
27
+ "DatabaseInstanceOutputs",
28
+ "DatabaseOutputs",
29
+ "User",
30
+ "UserConfig",
31
+ "UserOutputs",
32
+ ]
@@ -0,0 +1,155 @@
1
+ """GCP Cloud SQL database resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, ClassVar
6
+
7
+ from pragma_sdk import Config, Dependency, Outputs, Resource
8
+ from pydantic import Field
9
+
10
+ from gcp_provider.resources.cloudsql.database_instance import DatabaseInstance
11
+ from gcp_provider.resources.cloudsql.helpers import (
12
+ connection_info,
13
+ execute,
14
+ extract_ips,
15
+ get_credentials,
16
+ get_sqladmin_service,
17
+ )
18
+
19
+
20
+ class DatabaseConfig(Config):
21
+ """Configuration for a Cloud SQL database.
22
+
23
+ Attributes:
24
+ instance: The Cloud SQL instance that hosts this database.
25
+ database_name: Name of the database to create.
26
+ """
27
+
28
+ instance: Dependency[DatabaseInstance]
29
+ database_name: str = Field(json_schema_extra={"immutable": True})
30
+
31
+
32
+ class DatabaseOutputs(Outputs):
33
+ """Outputs from Cloud SQL database creation.
34
+
35
+ Attributes:
36
+ database_name: Name of the created database.
37
+ instance_name: Name of the hosting instance.
38
+ host: Database host (IP address or connection name).
39
+ port: Database port (5432 for postgres, 3306 for mysql).
40
+ url: Connection URL format (without credentials).
41
+ """
42
+
43
+ database_name: str
44
+ instance_name: str
45
+ host: str
46
+ port: int
47
+ url: str
48
+
49
+
50
+ class Database(Resource[DatabaseConfig, DatabaseOutputs]):
51
+ """GCP Cloud SQL database resource.
52
+
53
+ Creates and manages databases within a Cloud SQL instance.
54
+ Changing the instance triggers replacement (delete from old, create in new).
55
+ """
56
+
57
+ provider: ClassVar[str] = "gcp"
58
+ resource: ClassVar[str] = "cloudsql/database"
59
+
60
+ async def on_create(self) -> DatabaseOutputs:
61
+ """Create database in the Cloud SQL instance.
62
+
63
+ Idempotent: If database already exists, returns its current state.
64
+
65
+ Returns:
66
+ DatabaseOutputs with database details.
67
+ """
68
+ instance_resource = await self.config.instance.resolve()
69
+ inst = instance_resource.config
70
+ service = get_sqladmin_service(get_credentials(inst.credentials))
71
+
72
+ await execute(
73
+ service.databases().insert(
74
+ project=inst.project_id,
75
+ instance=inst.instance_name,
76
+ body={
77
+ "name": self.config.database_name,
78
+ "project": inst.project_id,
79
+ "instance": inst.instance_name,
80
+ },
81
+ ),
82
+ ignore_exists=True,
83
+ )
84
+
85
+ return await self._build_outputs(inst, service)
86
+
87
+ async def on_update(self, previous_config: DatabaseConfig) -> DatabaseOutputs:
88
+ """Handle database updates.
89
+
90
+ If instance changed, delete from old instance and create in new one.
91
+ database_name cannot be changed (truly immutable).
92
+
93
+ Returns:
94
+ DatabaseOutputs with database details.
95
+
96
+ Raises:
97
+ ValueError: If database_name changed (immutable field).
98
+ """
99
+ if previous_config.database_name != self.config.database_name:
100
+ msg = "Cannot change database_name; delete and recreate resource"
101
+ raise ValueError(msg)
102
+
103
+ if previous_config.instance != self.config.instance:
104
+ await self._delete(previous_config)
105
+ return await self.on_create()
106
+
107
+ instance_resource = await self.config.instance.resolve()
108
+ inst = instance_resource.config
109
+ service = get_sqladmin_service(get_credentials(inst.credentials))
110
+
111
+ return await self._build_outputs(inst, service)
112
+
113
+ async def on_delete(self) -> None:
114
+ """Delete database. Idempotent: succeeds if database doesn't exist."""
115
+ await self._delete(self.config)
116
+
117
+ async def _delete(self, config: DatabaseConfig) -> None:
118
+ """Delete database from instance. Idempotent: succeeds if not found."""
119
+ instance_resource = await config.instance.resolve()
120
+ inst = instance_resource.config
121
+ service = get_sqladmin_service(get_credentials(inst.credentials))
122
+
123
+ await execute(
124
+ service.databases().delete(
125
+ project=inst.project_id,
126
+ instance=inst.instance_name,
127
+ database=config.database_name,
128
+ ),
129
+ ignore_404=True,
130
+ )
131
+
132
+ async def _build_outputs(self, inst: Any, service: Any) -> DatabaseOutputs:
133
+ """Fetch instance info and build outputs.
134
+
135
+ Returns:
136
+ DatabaseOutputs with connection details.
137
+ """
138
+ instance = await execute(
139
+ service.instances().get(
140
+ project=inst.project_id,
141
+ instance=inst.instance_name,
142
+ )
143
+ )
144
+
145
+ public_ip, private_ip = extract_ips(instance)
146
+ db_type, db_port = connection_info(instance.get("databaseVersion", "POSTGRES_15"))
147
+ host = public_ip or private_ip or f"{inst.project_id}:{instance.get('region')}:{inst.instance_name}"
148
+
149
+ return DatabaseOutputs(
150
+ database_name=self.config.database_name,
151
+ instance_name=inst.instance_name,
152
+ host=host,
153
+ port=int(db_port),
154
+ url=f"{db_type}://{host}:{db_port}/{self.config.database_name}",
155
+ )