diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..ad099ea --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Global owners for all files in the repository +* @levivannoort diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..89f61ca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug Report +about: Report a bug in the Appwrite Terraform provider +title: "" +labels: bug +assignees: "" +--- + +## Description + +A clear and concise description of the bug. + +## Terraform Configuration + +```hcl +# Minimal Terraform configuration to reproduce the issue +``` + +## Expected Behavior + +What you expected to happen. + +## Actual Behavior + +What actually happened. Include any error messages. + +## Steps to Reproduce + +1. Run `terraform plan` with the above configuration +2. ... + +## Environment + +- Terraform version: (output of `terraform version`) +- Provider version: +- Appwrite version: (Cloud or Community Edition version) +- OS: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..1667a06 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,25 @@ +--- +name: Feature Request +about: Suggest a new resource, data source, or improvement +title: "" +labels: enhancement +assignees: "" +--- + +## Description + +A clear and concise description of the feature you'd like. + +## Use Case + +Why is this feature needed? What problem does it solve? + +## Proposed Configuration + +```hcl +# Example of how the feature would be used in Terraform +``` + +## Additional Context + +Any other context, links to Appwrite documentation, or screenshots. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..a211bcd --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ +## Description + +Brief description of the changes. + +## Type of Change + +- [ ] Bug fix +- [ ] New resource or data source +- [ ] Enhancement to existing resource +- [ ] Documentation +- [ ] Other + +## Checklist + +- [ ] `make lint` passes +- [ ] `make test` passes +- [ ] `make docs` has been run and changes committed +- [ ] Acceptance tests added/updated (for resource changes) +- [ ] Examples added/updated (for new resources/data sources) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcd5ecf..29a823f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,8 +32,10 @@ jobs: go-version-file: go.mod cache: true - - name: Go vet - run: go vet ./... + - name: golangci-lint + run: | + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + golangci-lint run - name: Check formatting run: | diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..4e630e6 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,86 @@ +linters: + enable: + # Default linters + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + + # Code style + - gofmt + - goimports + - misspell + - unconvert + - whitespace + + # Bug detection + - bodyclose + - durationcheck + - nilerr + - noctx + - rowserrcheck + - sqlclosecheck + - wastedassign + + # Code complexity & maintenance + - copyloopvar + - dupword + - errname + - errorlint + - fatcontext + - goconst + - goprintffuncname + - makezero + - nosprintfhostport + - prealloc + - predeclared + - reassign + - revive + - usetesting + - tparallel + - usestdlibvars + +linters-settings: + errcheck: + check-type-assertions: true + goconst: + min-len: 3 + min-occurrences: 3 + misspell: + locale: US + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + disabled: true + - name: increment-decrement + - name: var-naming + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + disabled: true + - name: unreachable-code + - name: redefines-builtin-id + +issues: + exclude-dirs: + - vendor + max-issues-per-linter: 0 + max-same-issues: 0 + +run: + timeout: 5m diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e123123 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,71 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Data sources for storage buckets, users, teams, functions, sites, topics, and webhooks +- Schema validators for deployment `source_type`, bucket `compression`, and function `name`/`runtime` +- User-Agent header (`terraform-provider-appwrite/`) on all API calls +- Acceptance tests for function and site deployment resources and data sources +- `CHANGELOG.md`, `CONTRIBUTING.md`, `SECURITY.md`, and GitHub issue templates +- `CODEOWNERS` file and pull request template +- `golangci-lint` in CI pipeline +- Import support for function and site deployment resources + +## [1.2.1] - 2026-04-17 + +### Fixed + +- Index creation resource handling +- Duplicate example files removed from the repository + +## [1.2.0] - 2026-04-17 + +### Changed + +- Upgraded `go-sdk` to `v3.0.0` with webhook field renames + +## [1.1.0] - 2026-04-17 + +### Added + +- Deployment resources for sites and functions +- Documentation and examples for deployment resources + +## [1.0.2] - 2026-04-17 + +### Changed + +- Polished repository README + +### Fixed + +- Added `WaitForColumnAvailable` preventing column creation errors +- Error handling for self-hosted API limitations + +## [1.0.1] - 2026-04-13 + +### Changed + +- Refactored documentation + +## [1.0.0] - 2026-04-12 + +### Added + +- Resources for functions and sites with their respective variables +- Cleaned up documentation + +## [0.0.8] - 2026-04-12 + +### Added + +- Initial release with support for TablesDB, Storage, Auth, Messaging, Webhooks, and Backup Policies +- Acceptance tests for all resources +- Auto-generated documentation via `terraform-plugin-docs` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f7f8c7a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,87 @@ +# Contributing to Terraform Provider for Appwrite + +Thank you for your interest in contributing! This document provides guidelines for contributing to the Appwrite Terraform provider. + +## Development Setup + +### Prerequisites + +- [Go](https://golang.org/doc/install) (see `go.mod` for required version) +- [Terraform](https://www.terraform.io/downloads.html) >= 1.0 +- An Appwrite instance (Cloud or Community Edition) for acceptance testing + +### Building + +```bash +make build +``` + +### Installing Locally + +```bash +make install +``` + +This installs the provider to `~/.terraform.d/plugins/` for local testing. + +### Running Tests + +Unit tests: + +```bash +make test +``` + +Acceptance tests (requires a running Appwrite instance): + +```bash +export APPWRITE_ENDPOINT="https://cloud.appwrite.io/v1" +export APPWRITE_PROJECT_ID="your-project-id" +export APPWRITE_API_KEY="your-api-key" +make acceptance-test +``` + +### Linting + +```bash +make lint +``` + +### Generating Documentation + +```bash +make docs +``` + +Documentation is auto-generated from schema definitions and examples using [terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs). Always run `make docs` after changing resource schemas or examples. + +## Adding a New Resource + +1. Create a new directory under `internal/services//` +2. Implement `resource.go` with the Plugin Framework interfaces +3. Add the resource to `provider.go` in the `Resources()` method +4. Create an example in `examples/resources/appwrite_/resource.tf` +5. Create an import example in `examples/resources/appwrite_/import.sh` +6. Create a doc template in `templates/resources/.md.tmpl` +7. Write acceptance tests in `resource_test.go` +8. Run `make docs` to generate documentation + +## Adding a New Data Source + +Follow the same pattern as resources, but use `datasource.DataSource` interface and register in the `DataSources()` method. + +## Pull Request Process + +1. Fork the repository and create a feature branch +2. Write or update tests for your changes +3. Run `make lint` and `make test` to verify your changes +4. Run `make docs` and commit any generated documentation changes +5. Open a pull request with a clear description of the changes + +## Code Style + +- Follow standard Go conventions (`gofmt`, `go vet`) +- Use the Terraform Plugin Framework (not SDK v2) for new resources +- Keep resource implementations consistent with existing patterns +- Mark sensitive fields with `Sensitive: true` +- Handle 404 errors in Read by calling `resp.State.RemoveResource(ctx)` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..e080264 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,24 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| latest | :white_check_mark: | + +## Reporting a Vulnerability + +If you discover a security vulnerability in this Terraform provider, please report it responsibly. + +**Do not open a public GitHub issue for security vulnerabilities.** + +Instead, please report vulnerabilities to [security@appwrite.io](mailto:security@appwrite.io). + +Include the following information: + +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if any) + +We will acknowledge receipt within 48 hours and provide a detailed response within 7 days. diff --git a/docs/data-sources/auth_team.md b/docs/data-sources/auth_team.md new file mode 100644 index 0000000..e72682b --- /dev/null +++ b/docs/data-sources/auth_team.md @@ -0,0 +1,39 @@ +--- +page_title: "Data Source: appwrite_auth_team" +subcategory: "Auth" +description: |- + Fetches an Appwrite team by ID. +--- + +# Data Source: appwrite_auth_team + +Fetches an Appwrite team by ID. + +## Example Usage + +```terraform +data "appwrite_auth_team" "engineers" { + id = "engineers" +} + +output "team_name" { + value = data.appwrite_auth_team.engineers.name +} +``` + + +## Schema + +### Required + +- `id` (String) The team ID. + +### Optional + +- `project_id` (String) The Appwrite project ID. Defaults to the provider-level project_id. + +### Read-Only + +- `created_at` (String) The team creation timestamp. +- `name` (String) The team name. +- `updated_at` (String) The team last update timestamp. diff --git a/docs/data-sources/auth_user.md b/docs/data-sources/auth_user.md new file mode 100644 index 0000000..4ff084e --- /dev/null +++ b/docs/data-sources/auth_user.md @@ -0,0 +1,45 @@ +--- +page_title: "Data Source: appwrite_auth_user" +subcategory: "Auth" +description: |- + Fetches an Appwrite user by ID. +--- + +# Data Source: appwrite_auth_user + +Fetches an Appwrite user by ID. + +## Example Usage + +```terraform +data "appwrite_auth_user" "admin" { + id = "64f2cd7e27bda9f23ab6" +} + +output "user_email" { + value = data.appwrite_auth_user.admin.email +} +``` + + +## Schema + +### Required + +- `id` (String) The user ID. + +### Optional + +- `project_id` (String) The Appwrite project ID. Defaults to the provider-level project_id. + +### Read-Only + +- `created_at` (String) The user creation timestamp. +- `email` (String) The user email address. +- `email_verification` (Boolean) Whether the user email is verified. +- `labels` (List of String) User labels. +- `name` (String) The user name. +- `phone` (String) The user phone number. +- `phone_verification` (Boolean) Whether the user phone is verified. +- `status` (Boolean) Whether the user account is enabled. +- `updated_at` (String) The user last update timestamp. diff --git a/docs/data-sources/function.md b/docs/data-sources/function.md new file mode 100644 index 0000000..924c7b0 --- /dev/null +++ b/docs/data-sources/function.md @@ -0,0 +1,50 @@ +--- +page_title: "Data Source: appwrite_function" +subcategory: "Functions" +description: |- + Fetches an Appwrite function by ID. +--- + +# Data Source: appwrite_function + +Fetches an Appwrite function by ID. + +## Example Usage + +```terraform +data "appwrite_function" "hello_world" { + id = "64f2cd7e27bda9f23ab6" +} + +output "function_runtime" { + value = data.appwrite_function.hello_world.runtime +} +``` + + +## Schema + +### Required + +- `id` (String) The function ID. + +### Optional + +- `project_id` (String) The Appwrite project ID. Defaults to the provider-level project_id. + +### Read-Only + +- `commands` (String) The build command. +- `created_at` (String) The function creation timestamp. +- `deployment_id` (String) The active deployment ID. +- `enabled` (Boolean) Whether the function is enabled. +- `entrypoint` (String) The entrypoint file. +- `events` (List of String) Events that trigger the function. +- `execute` (List of String) Execution permissions. +- `logging` (Boolean) Whether execution logs are enabled. +- `name` (String) The function name. +- `runtime` (String) The function execution runtime. +- `schedule` (String) Function execution schedule in CRON format. +- `scopes` (List of String) Function scopes. +- `timeout` (Number) Function execution timeout in seconds. +- `updated_at` (String) The function last update timestamp. diff --git a/docs/data-sources/messaging_topic.md b/docs/data-sources/messaging_topic.md new file mode 100644 index 0000000..259e54d --- /dev/null +++ b/docs/data-sources/messaging_topic.md @@ -0,0 +1,40 @@ +--- +page_title: "Data Source: appwrite_messaging_topic" +subcategory: "Messaging" +description: |- + Fetches an Appwrite messaging topic by ID. +--- + +# Data Source: appwrite_messaging_topic + +Fetches an Appwrite messaging topic by ID. + +## Example Usage + +```terraform +data "appwrite_messaging_topic" "notifications" { + id = "notifications" +} + +output "topic_name" { + value = data.appwrite_messaging_topic.notifications.name +} +``` + + +## Schema + +### Required + +- `id` (String) The topic ID. + +### Optional + +- `project_id` (String) The Appwrite project ID. Defaults to the provider-level project_id. + +### Read-Only + +- `created_at` (String) The topic creation timestamp. +- `name` (String) The topic name. +- `subscribe` (List of String) Subscribe permissions. +- `updated_at` (String) The topic last update timestamp. diff --git a/docs/data-sources/site.md b/docs/data-sources/site.md new file mode 100644 index 0000000..2f35207 --- /dev/null +++ b/docs/data-sources/site.md @@ -0,0 +1,48 @@ +--- +page_title: "Data Source: appwrite_site" +subcategory: "Sites" +description: |- + Fetches an Appwrite site by ID. +--- + +# Data Source: appwrite_site + +Fetches an Appwrite site by ID. + +## Example Usage + +```terraform +data "appwrite_site" "landing" { + id = "64f2cd7e27bda9f23ab6" +} + +output "site_framework" { + value = data.appwrite_site.landing.framework +} +``` + + +## Schema + +### Required + +- `id` (String) The site ID. + +### Optional + +- `project_id` (String) The Appwrite project ID. Defaults to the provider-level project_id. + +### Read-Only + +- `build_command` (String) Custom build command. +- `build_runtime` (String) The build runtime. +- `created_at` (String) The site creation timestamp. +- `deployment_id` (String) The active deployment ID. +- `enabled` (Boolean) Whether the site is enabled. +- `framework` (String) The site framework. +- `install_command` (String) Custom install command. +- `logging` (Boolean) Whether logging is enabled. +- `name` (String) The site name. +- `output_directory` (String) Build output directory. +- `timeout` (Number) Site timeout in seconds. +- `updated_at` (String) The site last update timestamp. diff --git a/docs/data-sources/storage_bucket.md b/docs/data-sources/storage_bucket.md new file mode 100644 index 0000000..5161a57 --- /dev/null +++ b/docs/data-sources/storage_bucket.md @@ -0,0 +1,46 @@ +--- +page_title: "Data Source: appwrite_storage_bucket" +subcategory: "Storage" +description: |- + Fetches an Appwrite storage bucket by ID. +--- + +# Data Source: appwrite_storage_bucket + +Fetches an Appwrite storage bucket by ID. + +## Example Usage + +```terraform +data "appwrite_storage_bucket" "uploads" { + id = "uploads" +} + +output "bucket_name" { + value = data.appwrite_storage_bucket.uploads.name +} +``` + + +## Schema + +### Required + +- `id` (String) The bucket ID. + +### Optional + +- `project_id` (String) The Appwrite project ID. Defaults to the provider-level project_id. + +### Read-Only + +- `allowed_file_extensions` (List of String) List of allowed file extensions. +- `antivirus` (Boolean) Whether virus scanning is enabled. +- `compression` (String) Compression algorithm. +- `created_at` (String) The bucket creation timestamp. +- `enabled` (Boolean) Whether the bucket is enabled. +- `encryption` (Boolean) Whether bucket encryption is enabled. +- `file_security` (Boolean) Whether file-level security is enabled. +- `maximum_file_size` (Number) Maximum file size in bytes. +- `name` (String) The bucket name. +- `updated_at` (String) The bucket last update timestamp. diff --git a/docs/data-sources/webhook.md b/docs/data-sources/webhook.md new file mode 100644 index 0000000..b124ff5 --- /dev/null +++ b/docs/data-sources/webhook.md @@ -0,0 +1,43 @@ +--- +page_title: "Data Source: appwrite_webhook" +subcategory: "Webhooks" +description: |- + Fetches an Appwrite webhook by ID. +--- + +# Data Source: appwrite_webhook + +Fetches an Appwrite webhook by ID. + +## Example Usage + +```terraform +data "appwrite_webhook" "slack" { + id = "64f2cd7e27bda9f23ab6" +} + +output "webhook_url" { + value = data.appwrite_webhook.slack.url +} +``` + + +## Schema + +### Required + +- `id` (String) The webhook ID. + +### Optional + +- `project_id` (String) The Appwrite project ID. Defaults to the provider-level project_id. + +### Read-Only + +- `created_at` (String) The webhook creation timestamp. +- `enabled` (Boolean) Whether the webhook is enabled. +- `events` (List of String) Events that trigger the webhook. +- `name` (String) The webhook name. +- `tls` (Boolean) Whether TLS verification is enabled. +- `updated_at` (String) The webhook last update timestamp. +- `url` (String) The webhook URL. diff --git a/docs/index.md b/docs/index.md index 7248fbd..3742488 100644 --- a/docs/index.md +++ b/docs/index.md @@ -97,3 +97,10 @@ The `project_id` can be set at the provider level as a default for all resources ## Data Sources - [appwrite_tablesdb](data-sources/tablesdb.md) - Fetch a database by ID +- [appwrite_storage_bucket](data-sources/storage_bucket.md) - Fetch a storage bucket by ID +- [appwrite_auth_user](data-sources/auth_user.md) - Fetch a user by ID +- [appwrite_auth_team](data-sources/auth_team.md) - Fetch a team by ID +- [appwrite_function](data-sources/function.md) - Fetch a function by ID +- [appwrite_site](data-sources/site.md) - Fetch a site by ID +- [appwrite_messaging_topic](data-sources/messaging_topic.md) - Fetch a messaging topic by ID +- [appwrite_webhook](data-sources/webhook.md) - Fetch a webhook by ID diff --git a/docs/resources/function_deployment.md b/docs/resources/function_deployment.md index 5be8139..9793158 100644 --- a/docs/resources/function_deployment.md +++ b/docs/resources/function_deployment.md @@ -9,35 +9,25 @@ description: |- Manages an Appwrite function deployment. -Deployments are immutable - any change to input attributes will destroy the existing deployment and create a new one. Use the `wait_for_ready` attribute (default `true`) to wait for the build to complete before Terraform proceeds. - --> **Tip:** Use `code_hash = filesha256("./code.tar.gz")` to trigger a new deployment when your code file changes, even if the file path stays the same. - ## Example Usage -### Code Upload - ```terraform -resource "appwrite_function_deployment" "process_order" { - function_id = appwrite_function.process_order.id +resource "appwrite_function_deployment" "from_code" { + function_id = appwrite_function.example.id source_type = "code" - code_path = "./process-order.tar.gz" - code_hash = filesha256("./process-order.tar.gz") + code_path = "dist/function.tar.gz" + code_hash = filesha256("dist/function.tar.gz") entrypoint = "index.js" commands = "npm install" activate = true } -``` - -### Template -```terraform -resource "appwrite_function_deployment" "daily_report" { - function_id = appwrite_function.daily_report.id +resource "appwrite_function_deployment" "from_template" { + function_id = appwrite_function.example.id source_type = "template" - repository = "templates" + repository = "starter-template" owner = "appwrite" - root_directory = "node/starter" + root_directory = "node" type = "branch" reference = "main" activate = true @@ -79,11 +69,10 @@ resource "appwrite_function_deployment" "daily_report" { - `total_size` (Number) The total size in bytes. - `updated_at` (String) The deployment last update timestamp in ISO 8601 format. +## Import +Import is supported using the following syntax: -~> **NOTE:** Imported deployments will not have `source_type`, `code_path`, or template attributes in state since these are not returned by the API. The imported resource can be read and deleted but not recreated without specifying these attributes. - -## See Also - -- [appwrite_function](function.md) - Manage functions -- [appwrite_function_variable](function_variable.md) - Manage function environment variables +```shell +terraform import appwrite_function_deployment.example function_id/deployment_id +``` diff --git a/docs/resources/site_deployment.md b/docs/resources/site_deployment.md index a2e52c6..3207da6 100644 --- a/docs/resources/site_deployment.md +++ b/docs/resources/site_deployment.md @@ -9,33 +9,23 @@ description: |- Manages an Appwrite site deployment. -Deployments are immutable - any change to input attributes will destroy the existing deployment and create a new one. Use the `wait_for_ready` attribute (default `true`) to wait for the build to complete before Terraform proceeds. - --> **Tip:** Use `code_hash = filesha256("./dist.tar.gz")` to trigger a new deployment when your code file changes, even if the file path stays the same. - ## Example Usage -### Code Upload - ```terraform -resource "appwrite_site_deployment" "landing_page" { - site_id = appwrite_site.landing_page.id +resource "appwrite_site_deployment" "from_code" { + site_id = appwrite_site.example.id source_type = "code" - code_path = "./dist.tar.gz" - code_hash = filesha256("./dist.tar.gz") + code_path = "dist/site.tar.gz" + code_hash = filesha256("dist/site.tar.gz") activate = true } -``` - -### Template -```terraform -resource "appwrite_site_deployment" "dashboard" { - site_id = appwrite_site.dashboard.id +resource "appwrite_site_deployment" "from_template" { + site_id = appwrite_site.example.id source_type = "template" - repository = "templates-for-sites" + repository = "starter-template" owner = "appwrite" - root_directory = "nextjs/starter" + root_directory = "nextjs" type = "branch" reference = "main" activate = true @@ -78,11 +68,10 @@ resource "appwrite_site_deployment" "dashboard" { - `total_size` (Number) The total size in bytes. - `updated_at` (String) The deployment last update timestamp in ISO 8601 format. +## Import +Import is supported using the following syntax: -~> **NOTE:** Imported deployments will not have `source_type`, `code_path`, or template attributes in state since these are not returned by the API. The imported resource can be read and deleted but not recreated without specifying these attributes. - -## See Also - -- [appwrite_site](site.md) - Manage sites -- [appwrite_site_variable](site_variable.md) - Manage site environment variables +```shell +terraform import appwrite_site_deployment.example site_id/deployment_id +``` diff --git a/docs/resources/webhook.md b/docs/resources/webhook.md index b58afc4..676aace 100644 --- a/docs/resources/webhook.md +++ b/docs/resources/webhook.md @@ -46,9 +46,9 @@ resource "appwrite_webhook" "authenticated" { ### Optional -- `enabled` (Boolean) Whether the webhook is enabled. Defaults to true. - `auth_password` (String, Sensitive) HTTP basic authentication password. - `auth_username` (String) HTTP basic authentication username. +- `enabled` (Boolean) Whether the webhook is enabled. Defaults to true. - `id` (String) The webhook ID. - `project_id` (String) The Appwrite project ID. Defaults to the provider-level project_id. - `tls` (Boolean) Whether SSL/TLS certificate verification is enabled. Defaults to false. diff --git a/examples/data-sources/appwrite_auth_team/data-source.tf b/examples/data-sources/appwrite_auth_team/data-source.tf new file mode 100644 index 0000000..d2366d6 --- /dev/null +++ b/examples/data-sources/appwrite_auth_team/data-source.tf @@ -0,0 +1,7 @@ +data "appwrite_auth_team" "engineers" { + id = "engineers" +} + +output "team_name" { + value = data.appwrite_auth_team.engineers.name +} diff --git a/examples/data-sources/appwrite_auth_user/data-source.tf b/examples/data-sources/appwrite_auth_user/data-source.tf new file mode 100644 index 0000000..1fa8ff7 --- /dev/null +++ b/examples/data-sources/appwrite_auth_user/data-source.tf @@ -0,0 +1,7 @@ +data "appwrite_auth_user" "admin" { + id = "64f2cd7e27bda9f23ab6" +} + +output "user_email" { + value = data.appwrite_auth_user.admin.email +} diff --git a/examples/data-sources/appwrite_function/data-source.tf b/examples/data-sources/appwrite_function/data-source.tf new file mode 100644 index 0000000..ab84b7a --- /dev/null +++ b/examples/data-sources/appwrite_function/data-source.tf @@ -0,0 +1,7 @@ +data "appwrite_function" "hello_world" { + id = "64f2cd7e27bda9f23ab6" +} + +output "function_runtime" { + value = data.appwrite_function.hello_world.runtime +} diff --git a/examples/data-sources/appwrite_messaging_topic/data-source.tf b/examples/data-sources/appwrite_messaging_topic/data-source.tf new file mode 100644 index 0000000..40cdc6e --- /dev/null +++ b/examples/data-sources/appwrite_messaging_topic/data-source.tf @@ -0,0 +1,7 @@ +data "appwrite_messaging_topic" "notifications" { + id = "notifications" +} + +output "topic_name" { + value = data.appwrite_messaging_topic.notifications.name +} diff --git a/examples/data-sources/appwrite_site/data-source.tf b/examples/data-sources/appwrite_site/data-source.tf new file mode 100644 index 0000000..2d26cd1 --- /dev/null +++ b/examples/data-sources/appwrite_site/data-source.tf @@ -0,0 +1,7 @@ +data "appwrite_site" "landing" { + id = "64f2cd7e27bda9f23ab6" +} + +output "site_framework" { + value = data.appwrite_site.landing.framework +} diff --git a/examples/data-sources/appwrite_storage_bucket/data-source.tf b/examples/data-sources/appwrite_storage_bucket/data-source.tf new file mode 100644 index 0000000..9ca26f4 --- /dev/null +++ b/examples/data-sources/appwrite_storage_bucket/data-source.tf @@ -0,0 +1,7 @@ +data "appwrite_storage_bucket" "uploads" { + id = "uploads" +} + +output "bucket_name" { + value = data.appwrite_storage_bucket.uploads.name +} diff --git a/examples/data-sources/appwrite_webhook/data-source.tf b/examples/data-sources/appwrite_webhook/data-source.tf new file mode 100644 index 0000000..a088c7f --- /dev/null +++ b/examples/data-sources/appwrite_webhook/data-source.tf @@ -0,0 +1,7 @@ +data "appwrite_webhook" "slack" { + id = "64f2cd7e27bda9f23ab6" +} + +output "webhook_url" { + value = data.appwrite_webhook.slack.url +} diff --git a/examples/resources/appwrite_function_deployment/import.sh b/examples/resources/appwrite_function_deployment/import.sh new file mode 100644 index 0000000..5c3faf4 --- /dev/null +++ b/examples/resources/appwrite_function_deployment/import.sh @@ -0,0 +1 @@ +terraform import appwrite_function_deployment.example function_id/deployment_id diff --git a/examples/resources/appwrite_function_deployment/resource.tf b/examples/resources/appwrite_function_deployment/resource.tf new file mode 100644 index 0000000..167ec6b --- /dev/null +++ b/examples/resources/appwrite_function_deployment/resource.tf @@ -0,0 +1,20 @@ +resource "appwrite_function_deployment" "from_code" { + function_id = appwrite_function.example.id + source_type = "code" + code_path = "dist/function.tar.gz" + code_hash = filesha256("dist/function.tar.gz") + entrypoint = "index.js" + commands = "npm install" + activate = true +} + +resource "appwrite_function_deployment" "from_template" { + function_id = appwrite_function.example.id + source_type = "template" + repository = "starter-template" + owner = "appwrite" + root_directory = "node" + type = "branch" + reference = "main" + activate = true +} diff --git a/examples/resources/appwrite_site_deployment/import.sh b/examples/resources/appwrite_site_deployment/import.sh new file mode 100644 index 0000000..6b791db --- /dev/null +++ b/examples/resources/appwrite_site_deployment/import.sh @@ -0,0 +1 @@ +terraform import appwrite_site_deployment.example site_id/deployment_id diff --git a/examples/resources/appwrite_site_deployment/resource.tf b/examples/resources/appwrite_site_deployment/resource.tf new file mode 100644 index 0000000..332be61 --- /dev/null +++ b/examples/resources/appwrite_site_deployment/resource.tf @@ -0,0 +1,18 @@ +resource "appwrite_site_deployment" "from_code" { + site_id = appwrite_site.example.id + source_type = "code" + code_path = "dist/site.tar.gz" + code_hash = filesha256("dist/site.tar.gz") + activate = true +} + +resource "appwrite_site_deployment" "from_template" { + site_id = appwrite_site.example.id + source_type = "template" + repository = "starter-template" + owner = "appwrite" + root_directory = "nextjs" + type = "branch" + reference = "main" + activate = true +} diff --git a/go.mod b/go.mod index b248d46..5138cd2 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/appwrite/sdk-for-go/v3 v3.0.0 github.com/hashicorp/terraform-plugin-docs v0.24.0 github.com/hashicorp/terraform-plugin-framework v1.19.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 github.com/hashicorp/terraform-plugin-go v0.31.0 github.com/hashicorp/terraform-plugin-testing v1.15.0 ) diff --git a/go.sum b/go.sum index e8ed678..267dded 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/hashicorp/terraform-plugin-docs v0.24.0 h1:YNZYd+8cpYclQyXbl1EEngbld8 github.com/hashicorp/terraform-plugin-docs v0.24.0/go.mod h1:YLg+7LEwVmRuJc0EuCw0SPLxuQXw5mW8iJ5ml/kvi+o= github.com/hashicorp/terraform-plugin-framework v1.19.0 h1:q0bwyhxAOR3vfdgbk9iplv3MlTv/dhBHTXjQOtQDoBA= github.com/hashicorp/terraform-plugin-framework v1.19.0/go.mod h1:YRXOBu0jvs7xp4AThBbX4mAzYaMJ1JgtFH//oGKxwLc= +github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow= +github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc= github.com/hashicorp/terraform-plugin-go v0.31.0 h1:0Fz2r9DQ+kNNl6bx8HRxFd1TfMKUvnrOtvJPmp3Z0q8= github.com/hashicorp/terraform-plugin-go v0.31.0/go.mod h1:A88bDhd/cW7FnwqxQRz3slT+QY6yzbHKc6AOTtmdeS8= github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g= diff --git a/internal/common/helpers.go b/internal/common/helpers.go index 38a4528..7b227a2 100644 --- a/internal/common/helpers.go +++ b/internal/common/helpers.go @@ -3,6 +3,7 @@ package common import ( "context" "encoding/json" + "errors" "fmt" "strings" "time" @@ -25,10 +26,19 @@ type AppwriteClients struct { ProjectID string } +// WithUserAgent returns a ClientOption that sets the User-Agent header to identify +// Terraform provider traffic. This is required for HashiCorp partner providers. +func WithUserAgent(version string) client.ClientOption { + return func(clt *client.Client) error { + clt.Headers["user-agent"] = fmt.Sprintf("terraform-provider-appwrite/%s", version) + return nil + } +} + // ClientForProject creates a new SDK client targeting a specific project. func (ac *AppwriteClients) ClientForProject(projectID string) client.Client { - opts := make([]client.ClientOption, len(ac.BaseOptions)) - copy(opts, ac.BaseOptions) + opts := make([]client.ClientOption, 0, len(ac.BaseOptions)+1) + opts = append(opts, ac.BaseOptions...) opts = append(opts, appwrite.WithProject(projectID)) return appwrite.NewClient(opts...) } @@ -57,7 +67,8 @@ func ProjectIDAttribute() schema.StringAttribute { // IsNotFoundError checks if an Appwrite SDK error is a 404. func IsNotFoundError(err error) bool { - if appErr, ok := err.(*client.AppwriteError); ok { + var appErr *client.AppwriteError + if errors.As(err, &appErr) { return appErr.GetStatusCode() == 404 } return false @@ -65,7 +76,8 @@ func IsNotFoundError(err error) bool { // IsColumnNotAvailableError checks if the error is due to a column still being processed. func IsColumnNotAvailableError(err error) bool { - if appErr, ok := err.(*client.AppwriteError); ok { + var appErr *client.AppwriteError + if errors.As(err, &appErr) { return appErr.GetStatusCode() == 400 && strings.Contains(appErr.GetMessage(), "not yet available") } return false @@ -73,7 +85,8 @@ func IsColumnNotAvailableError(err error) bool { // FormatError returns a detailed error string including status code and response body. func FormatError(err error) string { - if appErr, ok := err.(*client.AppwriteError); ok { + var appErr *client.AppwriteError + if errors.As(err, &appErr) { return fmt.Sprintf("%s (status: %d, response: %s)", appErr.GetMessage(), appErr.GetStatusCode(), appErr.GetResponse()) } return err.Error() @@ -95,8 +108,12 @@ func GetColumnRaw(c client.Client, databaseID, tableID, key string) (map[string] if !strings.HasPrefix(resp.Type, "application/json") { return nil, fmt.Errorf("unexpected response type: %s", resp.Type) } + resultStr, ok := resp.Result.(string) + if !ok { + return nil, fmt.Errorf("unexpected response result type: %T", resp.Result) + } var result map[string]interface{} - if err := json.Unmarshal([]byte(resp.Result.(string)), &result); err != nil { + if err := json.Unmarshal([]byte(resultStr), &result); err != nil { return nil, fmt.Errorf("failed to unmarshal column response: %w", err) } return result, nil diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 96140e5..fcf6ddb 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -110,6 +110,7 @@ func (p *appwriteProvider) Configure(ctx context.Context, req provider.Configure baseOpts := []client.ClientOption{ appwrite.WithEndpoint(endpoint), appwrite.WithKey(apiKey), + common.WithUserAgent(p.version), } if !config.SelfSigned.IsNull() && config.SelfSigned.ValueBool() { baseOpts = append(baseOpts, appwrite.WithSelfSigned(true)) @@ -152,6 +153,13 @@ func (p *appwriteProvider) Resources(_ context.Context) []func() resource.Resour func (p *appwriteProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ databasesvc.NewDatabaseDataSource, + bucketsvc.NewBucketDataSource, + usersvc.NewUserDataSource, + teamsvc.NewTeamDataSource, + functionsvc.NewFunctionDataSource, + sitesvc.NewSiteDataSource, + topicsvc.NewTopicDataSource, + webhooksvc.NewWebhookDataSource, } } diff --git a/internal/services/bucket/data_source.go b/internal/services/bucket/data_source.go new file mode 100644 index 0000000..e6a19c1 --- /dev/null +++ b/internal/services/bucket/data_source.go @@ -0,0 +1,156 @@ +package bucket + +import ( + "context" + "fmt" + + "github.com/appwrite/sdk-for-go/v3/appwrite" + "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &bucketDataSource{} + _ datasource.DataSourceWithConfigure = &bucketDataSource{} +) + +type bucketDataSource struct { + clients *common.AppwriteClients +} + +type bucketDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Enabled types.Bool `tfsdk:"enabled"` + MaximumFileSize types.Int64 `tfsdk:"maximum_file_size"` + AllowedFileExtensions types.List `tfsdk:"allowed_file_extensions"` + FileSecurity types.Bool `tfsdk:"file_security"` + Compression types.String `tfsdk:"compression"` + Encryption types.Bool `tfsdk:"encryption"` + Antivirus types.Bool `tfsdk:"antivirus"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + ProjectID types.String `tfsdk:"project_id"` +} + +func NewBucketDataSource() datasource.DataSource { + return &bucketDataSource{} +} + +func (d *bucketDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_storage_bucket" +} + +func (d *bucketDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Fetches an Appwrite storage bucket by ID.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The bucket ID.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The bucket name.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether the bucket is enabled.", + Computed: true, + }, + "maximum_file_size": schema.Int64Attribute{ + Description: "Maximum file size in bytes.", + Computed: true, + }, + "allowed_file_extensions": schema.ListAttribute{ + Description: "List of allowed file extensions.", + Computed: true, + ElementType: types.StringType, + }, + "file_security": schema.BoolAttribute{ + Description: "Whether file-level security is enabled.", + Computed: true, + }, + "compression": schema.StringAttribute{ + Description: "Compression algorithm.", + Computed: true, + }, + "encryption": schema.BoolAttribute{ + Description: "Whether bucket encryption is enabled.", + Computed: true, + }, + "antivirus": schema.BoolAttribute{ + Description: "Whether virus scanning is enabled.", + Computed: true, + }, + "created_at": schema.StringAttribute{ + Description: "The bucket creation timestamp.", + Computed: true, + }, + "updated_at": schema.StringAttribute{ + Description: "The bucket last update timestamp.", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "The Appwrite project ID. Defaults to the provider-level project_id.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (d *bucketDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + clients, ok := req.ProviderData.(*common.AppwriteClients) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *common.AppwriteClients, got: %T", req.ProviderData), + ) + return + } + d.clients = clients +} + +func (d *bucketDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config bucketDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + projectID, err := common.ResolveProjectID(d.clients, config.ProjectID) + if err != nil { + resp.Diagnostics.AddError("Error resolving project ID", err.Error()) + return + } + storageClient := appwrite.NewStorage(d.clients.ClientForProject(projectID)) + + bucket, err := storageClient.GetBucket(config.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error reading bucket", common.FormatError(err)) + return + } + + config.ProjectID = types.StringValue(projectID) + config.ID = types.StringValue(bucket.Id) + config.Name = types.StringValue(bucket.Name) + config.Enabled = types.BoolValue(bucket.Enabled) + config.MaximumFileSize = types.Int64Value(int64(bucket.MaximumFileSize)) + config.FileSecurity = types.BoolValue(bucket.FileSecurity) + config.Compression = types.StringValue(bucket.Compression) + config.Encryption = types.BoolValue(bucket.Encryption) + config.Antivirus = types.BoolValue(bucket.Antivirus) + config.CreatedAt = types.StringValue(bucket.CreatedAt) + config.UpdatedAt = types.StringValue(bucket.UpdatedAt) + + extList, diags := types.ListValueFrom(ctx, types.StringType, bucket.AllowedFileExtensions) + resp.Diagnostics.Append(diags...) + config.AllowedFileExtensions = extList + + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/internal/services/bucket/data_source_test.go b/internal/services/bucket/data_source_test.go new file mode 100644 index 0000000..203586c --- /dev/null +++ b/internal/services/bucket/data_source_test.go @@ -0,0 +1,34 @@ +package bucket_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccBucketDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_storage_bucket" "test" { + id = "ds-test-bucket" + name = "DS Test Bucket" +} + +data "appwrite_storage_bucket" "test" { + id = appwrite_storage_bucket.test.id +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.appwrite_storage_bucket.test", "name", "DS Test Bucket"), + resource.TestCheckResourceAttr("data.appwrite_storage_bucket.test", "enabled", "true"), + resource.TestCheckResourceAttrSet("data.appwrite_storage_bucket.test", "created_at"), + ), + }, + }, + }) +} diff --git a/internal/services/bucket/resource.go b/internal/services/bucket/resource.go index 830307b..8f952cf 100644 --- a/internal/services/bucket/resource.go +++ b/internal/services/bucket/resource.go @@ -9,6 +9,7 @@ import ( "github.com/appwrite/sdk-for-go/v3/models" "github.com/appwrite/sdk-for-go/v3/storage" "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -17,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -104,6 +106,7 @@ func (r *bucketResource) Schema(_ context.Context, _ resource.SchemaRequest, res Optional: true, Computed: true, Default: stringdefault.StaticString("none"), + Validators: []validator.String{stringvalidator.OneOf("none", "gzip", "zstd")}, }, "encryption": schema.BoolAttribute{ Description: "Whether bucket encryption is enabled. Defaults to true.", diff --git a/internal/services/column/resource.go b/internal/services/column/resource.go index e551905..855b48d 100644 --- a/internal/services/column/resource.go +++ b/internal/services/column/resource.go @@ -24,6 +24,11 @@ var ( _ resource.ResourceWithImportState = &columnResource{} ) +const ( + colTypeInteger = "integer" + colTypeFloat = "float" +) + var allColumnTypes = "varchar, text, longtext, mediumtext, integer, float, boolean, enum, email, datetime, url, ip, point, line, polygon, relationship, string" type columnResource struct { @@ -196,8 +201,8 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, } tablesdbClient := appwrite.NewTablesDB(r.clients.ClientForProject(projectID)) - databaseId := plan.DatabaseID.ValueString() - tableId := plan.TableID.ValueString() + databaseID := plan.DatabaseID.ValueString() + tableID := plan.TableID.ValueString() key := plan.Key.ValueString() if plan.Key.IsNull() || plan.Key.IsUnknown() { key = id.Unique() @@ -210,19 +215,15 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, switch columnType { case "string": - if plan.Size.IsNull() { - resp.Diagnostics.AddError("Missing attribute", "size is required for string columns") - return - } - var opts []tablesdb.CreateStringColumnOption + var opts []tablesdb.CreateTextColumnOption if !plan.DefaultStr.IsNull() { - opts = append(opts, tablesdbClient.WithCreateStringColumnDefault(plan.DefaultStr.ValueString())) + opts = append(opts, tablesdbClient.WithCreateTextColumnDefault(plan.DefaultStr.ValueString())) } - opts = append(opts, tablesdbClient.WithCreateStringColumnArray(array)) + opts = append(opts, tablesdbClient.WithCreateTextColumnArray(array)) if !plan.Encrypt.IsNull() && !plan.Encrypt.IsUnknown() { - opts = append(opts, tablesdbClient.WithCreateStringColumnEncrypt(plan.Encrypt.ValueBool())) + opts = append(opts, tablesdbClient.WithCreateTextColumnEncrypt(plan.Encrypt.ValueBool())) } - col, e := tablesdbClient.CreateStringColumn(databaseId, tableId, key, int(plan.Size.ValueInt64()), required, opts...) + col, e := tablesdbClient.CreateTextColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -241,7 +242,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, if !plan.Encrypt.IsNull() && !plan.Encrypt.IsUnknown() { opts = append(opts, tablesdbClient.WithCreateVarcharColumnEncrypt(plan.Encrypt.ValueBool())) } - col, e := tablesdbClient.CreateVarcharColumn(databaseId, tableId, key, int(plan.Size.ValueInt64()), required, opts...) + col, e := tablesdbClient.CreateVarcharColumn(databaseID, tableID, key, int(plan.Size.ValueInt64()), required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -256,7 +257,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, if !plan.Encrypt.IsNull() && !plan.Encrypt.IsUnknown() { opts = append(opts, tablesdbClient.WithCreateTextColumnEncrypt(plan.Encrypt.ValueBool())) } - col, e := tablesdbClient.CreateTextColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateTextColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -271,7 +272,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, if !plan.Encrypt.IsNull() && !plan.Encrypt.IsUnknown() { opts = append(opts, tablesdbClient.WithCreateLongtextColumnEncrypt(plan.Encrypt.ValueBool())) } - col, e := tablesdbClient.CreateLongtextColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateLongtextColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -286,13 +287,13 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, if !plan.Encrypt.IsNull() && !plan.Encrypt.IsUnknown() { opts = append(opts, tablesdbClient.WithCreateMediumtextColumnEncrypt(plan.Encrypt.ValueBool())) } - col, e := tablesdbClient.CreateMediumtextColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateMediumtextColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) } - case "integer": + case colTypeInteger: var opts []tablesdb.CreateIntegerColumnOption if !plan.Min.IsNull() { opts = append(opts, tablesdbClient.WithCreateIntegerColumnMin(int(plan.Min.ValueInt64()))) @@ -304,13 +305,13 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateIntegerColumnDefault(parseIntDefault(plan.DefaultStr.ValueString()))) } opts = append(opts, tablesdbClient.WithCreateIntegerColumnArray(array)) - col, e := tablesdbClient.CreateIntegerColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateIntegerColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) } - case "float": + case colTypeFloat: var opts []tablesdb.CreateFloatColumnOption if !plan.FloatMin.IsNull() { opts = append(opts, tablesdbClient.WithCreateFloatColumnMin(plan.FloatMin.ValueFloat64())) @@ -322,7 +323,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateFloatColumnDefault(parseFloatDefault(plan.DefaultStr.ValueString()))) } opts = append(opts, tablesdbClient.WithCreateFloatColumnArray(array)) - col, e := tablesdbClient.CreateFloatColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateFloatColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -334,7 +335,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateBooleanColumnDefault(plan.DefaultStr.ValueString() == "true")) } opts = append(opts, tablesdbClient.WithCreateBooleanColumnArray(array)) - col, e := tablesdbClient.CreateBooleanColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateBooleanColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -355,7 +356,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateEnumColumnDefault(plan.DefaultStr.ValueString())) } opts = append(opts, tablesdbClient.WithCreateEnumColumnArray(array)) - col, e := tablesdbClient.CreateEnumColumn(databaseId, tableId, key, elements, required, opts...) + col, e := tablesdbClient.CreateEnumColumn(databaseID, tableID, key, elements, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -367,7 +368,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateEmailColumnDefault(plan.DefaultStr.ValueString())) } opts = append(opts, tablesdbClient.WithCreateEmailColumnArray(array)) - col, e := tablesdbClient.CreateEmailColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateEmailColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -379,7 +380,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateDatetimeColumnDefault(plan.DefaultStr.ValueString())) } opts = append(opts, tablesdbClient.WithCreateDatetimeColumnArray(array)) - col, e := tablesdbClient.CreateDatetimeColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateDatetimeColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -391,7 +392,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateUrlColumnDefault(plan.DefaultStr.ValueString())) } opts = append(opts, tablesdbClient.WithCreateUrlColumnArray(array)) - col, e := tablesdbClient.CreateUrlColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateUrlColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -403,28 +404,28 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateIpColumnDefault(plan.DefaultStr.ValueString())) } opts = append(opts, tablesdbClient.WithCreateIpColumnArray(array)) - col, e := tablesdbClient.CreateIpColumn(databaseId, tableId, key, required, opts...) + col, e := tablesdbClient.CreateIpColumn(databaseID, tableID, key, required, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "point": - col, e := tablesdbClient.CreatePointColumn(databaseId, tableId, key, required) + col, e := tablesdbClient.CreatePointColumn(databaseID, tableID, key, required) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "line": - col, e := tablesdbClient.CreateLineColumn(databaseId, tableId, key, required) + col, e := tablesdbClient.CreateLineColumn(databaseID, tableID, key, required) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "polygon": - col, e := tablesdbClient.CreatePolygonColumn(databaseId, tableId, key, required) + col, e := tablesdbClient.CreatePolygonColumn(databaseID, tableID, key, required) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -452,7 +453,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, if !plan.OnDelete.IsNull() { opts = append(opts, tablesdbClient.WithCreateRelationshipColumnOnDelete(plan.OnDelete.ValueString())) } - col, e := tablesdbClient.CreateRelationshipColumn(databaseId, tableId, plan.RelatedTableID.ValueString(), plan.RelationType.ValueString(), opts...) + col, e := tablesdbClient.CreateRelationshipColumn(databaseID, tableID, plan.RelatedTableID.ValueString(), plan.RelationType.ValueString(), opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -469,7 +470,7 @@ func (r *columnResource) Create(ctx context.Context, req resource.CreateRequest, } if err := common.WaitForColumnAvailable(ctx, func() (interface{}, error) { - return common.GetColumnRaw(r.clients.ClientForProject(projectID), databaseId, tableId, key) + return common.GetColumnRaw(r.clients.ClientForProject(projectID), databaseID, tableID, key) }, key); err != nil { resp.Diagnostics.AddError("Error waiting for column to become available", err.Error()) return @@ -524,8 +525,8 @@ func (r *columnResource) Update(ctx context.Context, req resource.UpdateRequest, } tablesdbClient := appwrite.NewTablesDB(r.clients.ClientForProject(projectID)) - databaseId := plan.DatabaseID.ValueString() - tableId := plan.TableID.ValueString() + databaseID := plan.DatabaseID.ValueString() + tableID := plan.TableID.ValueString() key := plan.Key.ValueString() required := plan.Required.ValueBool() columnType := plan.Type.ValueString() @@ -539,11 +540,8 @@ func (r *columnResource) Update(ctx context.Context, req resource.UpdateRequest, switch columnType { case "string": - var opts []tablesdb.UpdateStringColumnOption - if !plan.Size.IsNull() { - opts = append(opts, tablesdbClient.WithUpdateStringColumnSize(int(plan.Size.ValueInt64()))) - } - col, e := tablesdbClient.UpdateStringColumn(databaseId, tableId, key, required, defaultStr, opts...) + var opts []tablesdb.UpdateTextColumnOption + col, e := tablesdbClient.UpdateTextColumn(databaseID, tableID, key, required, defaultStr, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -554,34 +552,34 @@ func (r *columnResource) Update(ctx context.Context, req resource.UpdateRequest, if !plan.Size.IsNull() { opts = append(opts, tablesdbClient.WithUpdateVarcharColumnSize(int(plan.Size.ValueInt64()))) } - col, e := tablesdbClient.UpdateVarcharColumn(databaseId, tableId, key, required, defaultStr, opts...) + col, e := tablesdbClient.UpdateVarcharColumn(databaseID, tableID, key, required, defaultStr, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "text": - col, e := tablesdbClient.UpdateTextColumn(databaseId, tableId, key, required, defaultStr) + col, e := tablesdbClient.UpdateTextColumn(databaseID, tableID, key, required, defaultStr) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "longtext": - col, e := tablesdbClient.UpdateLongtextColumn(databaseId, tableId, key, required, defaultStr) + col, e := tablesdbClient.UpdateLongtextColumn(databaseID, tableID, key, required, defaultStr) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "mediumtext": - col, e := tablesdbClient.UpdateMediumtextColumn(databaseId, tableId, key, required, defaultStr) + col, e := tablesdbClient.UpdateMediumtextColumn(databaseID, tableID, key, required, defaultStr) err = e if col != nil { responseJSON, _ = json.Marshal(col) } - case "integer": + case colTypeInteger: var opts []tablesdb.UpdateIntegerColumnOption if !plan.Min.IsNull() { opts = append(opts, tablesdbClient.WithUpdateIntegerColumnMin(int(plan.Min.ValueInt64()))) @@ -589,13 +587,13 @@ func (r *columnResource) Update(ctx context.Context, req resource.UpdateRequest, if !plan.Max.IsNull() { opts = append(opts, tablesdbClient.WithUpdateIntegerColumnMax(int(plan.Max.ValueInt64()))) } - col, e := tablesdbClient.UpdateIntegerColumn(databaseId, tableId, key, required, parseIntDefault(defaultStr), opts...) + col, e := tablesdbClient.UpdateIntegerColumn(databaseID, tableID, key, required, parseIntDefault(defaultStr), opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) } - case "float": + case colTypeFloat: var opts []tablesdb.UpdateFloatColumnOption if !plan.FloatMin.IsNull() { opts = append(opts, tablesdbClient.WithUpdateFloatColumnMin(plan.FloatMin.ValueFloat64())) @@ -603,14 +601,14 @@ func (r *columnResource) Update(ctx context.Context, req resource.UpdateRequest, if !plan.FloatMax.IsNull() { opts = append(opts, tablesdbClient.WithUpdateFloatColumnMax(plan.FloatMax.ValueFloat64())) } - col, e := tablesdbClient.UpdateFloatColumn(databaseId, tableId, key, required, parseFloatDefault(defaultStr), opts...) + col, e := tablesdbClient.UpdateFloatColumn(databaseID, tableID, key, required, parseFloatDefault(defaultStr), opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "boolean": - col, e := tablesdbClient.UpdateBooleanColumn(databaseId, tableId, key, required, defaultStr == "true") + col, e := tablesdbClient.UpdateBooleanColumn(databaseID, tableID, key, required, defaultStr == "true") err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -622,56 +620,56 @@ func (r *columnResource) Update(ctx context.Context, req resource.UpdateRequest, if resp.Diagnostics.HasError() { return } - col, e := tablesdbClient.UpdateEnumColumn(databaseId, tableId, key, elements, required, defaultStr) + col, e := tablesdbClient.UpdateEnumColumn(databaseID, tableID, key, elements, required, defaultStr) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "email": - col, e := tablesdbClient.UpdateEmailColumn(databaseId, tableId, key, required, defaultStr) + col, e := tablesdbClient.UpdateEmailColumn(databaseID, tableID, key, required, defaultStr) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "datetime": - col, e := tablesdbClient.UpdateDatetimeColumn(databaseId, tableId, key, required, defaultStr) + col, e := tablesdbClient.UpdateDatetimeColumn(databaseID, tableID, key, required, defaultStr) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "url": - col, e := tablesdbClient.UpdateUrlColumn(databaseId, tableId, key, required, defaultStr) + col, e := tablesdbClient.UpdateUrlColumn(databaseID, tableID, key, required, defaultStr) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "ip": - col, e := tablesdbClient.UpdateIpColumn(databaseId, tableId, key, required, defaultStr) + col, e := tablesdbClient.UpdateIpColumn(databaseID, tableID, key, required, defaultStr) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "point": - col, e := tablesdbClient.UpdatePointColumn(databaseId, tableId, key, required) + col, e := tablesdbClient.UpdatePointColumn(databaseID, tableID, key, required) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "line": - col, e := tablesdbClient.UpdateLineColumn(databaseId, tableId, key, required) + col, e := tablesdbClient.UpdateLineColumn(databaseID, tableID, key, required) err = e if col != nil { responseJSON, _ = json.Marshal(col) } case "polygon": - col, e := tablesdbClient.UpdatePolygonColumn(databaseId, tableId, key, required) + col, e := tablesdbClient.UpdatePolygonColumn(databaseID, tableID, key, required) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -682,7 +680,7 @@ func (r *columnResource) Update(ctx context.Context, req resource.UpdateRequest, if !plan.OnDelete.IsNull() { opts = append(opts, tablesdbClient.WithUpdateRelationshipColumnOnDelete(plan.OnDelete.ValueString())) } - col, e := tablesdbClient.UpdateRelationshipColumn(databaseId, tableId, key, opts...) + col, e := tablesdbClient.UpdateRelationshipColumn(databaseID, tableID, key, opts...) err = e if col != nil { responseJSON, _ = json.Marshal(col) @@ -751,20 +749,20 @@ func (r *columnResource) readResponseIntoState(ctx context.Context, responseJSON if size, ok := generic["size"].(float64); ok && size > 0 { model.Size = types.Int64Value(int64(size)) } - if min, ok := generic["min"].(float64); ok { + if minVal, ok := generic["min"].(float64); ok { columnType := model.Type.ValueString() - if columnType == "integer" { - model.Min = types.Int64Value(int64(min)) - } else if columnType == "float" { - model.FloatMin = types.Float64Value(min) + if columnType == colTypeInteger { + model.Min = types.Int64Value(int64(minVal)) + } else if columnType == colTypeFloat { + model.FloatMin = types.Float64Value(minVal) } } - if max, ok := generic["max"].(float64); ok { + if maxVal, ok := generic["max"].(float64); ok { columnType := model.Type.ValueString() - if columnType == "integer" { - model.Max = types.Int64Value(int64(max)) - } else if columnType == "float" { - model.FloatMax = types.Float64Value(max) + if columnType == colTypeInteger { + model.Max = types.Int64Value(int64(maxVal)) + } else if columnType == colTypeFloat { + model.FloatMax = types.Float64Value(maxVal) } } if encrypt, ok := generic["encrypt"].(bool); ok { @@ -818,7 +816,7 @@ func (r *columnResource) readResponseIntoState(ctx context.Context, responseJSON } case float64: if !model.DefaultStr.IsNull() { - if model.Type.ValueString() == "integer" { + if model.Type.ValueString() == colTypeInteger { model.DefaultStr = types.StringValue(fmt.Sprintf("%d", int64(v))) } else { model.DefaultStr = types.StringValue(fmt.Sprintf("%g", v)) diff --git a/internal/services/function/data_source.go b/internal/services/function/data_source.go new file mode 100644 index 0000000..9227ff0 --- /dev/null +++ b/internal/services/function/data_source.go @@ -0,0 +1,195 @@ +package function + +import ( + "context" + "fmt" + + "github.com/appwrite/sdk-for-go/v3/appwrite" + "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &functionDataSource{} + _ datasource.DataSourceWithConfigure = &functionDataSource{} +) + +type functionDataSource struct { + clients *common.AppwriteClients +} + +type functionDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Runtime types.String `tfsdk:"runtime"` + Enabled types.Bool `tfsdk:"enabled"` + Logging types.Bool `tfsdk:"logging"` + Entrypoint types.String `tfsdk:"entrypoint"` + Commands types.String `tfsdk:"commands"` + Schedule types.String `tfsdk:"schedule"` + Timeout types.Int64 `tfsdk:"timeout"` + Execute types.List `tfsdk:"execute"` + Events types.List `tfsdk:"events"` + Scopes types.List `tfsdk:"scopes"` + DeploymentID types.String `tfsdk:"deployment_id"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + ProjectID types.String `tfsdk:"project_id"` +} + +func NewFunctionDataSource() datasource.DataSource { + return &functionDataSource{} +} + +func (d *functionDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_function" +} + +func (d *functionDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Fetches an Appwrite function by ID.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The function ID.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The function name.", + Computed: true, + }, + "runtime": schema.StringAttribute{ + Description: "The function execution runtime.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether the function is enabled.", + Computed: true, + }, + "logging": schema.BoolAttribute{ + Description: "Whether execution logs are enabled.", + Computed: true, + }, + "entrypoint": schema.StringAttribute{ + Description: "The entrypoint file.", + Computed: true, + }, + "commands": schema.StringAttribute{ + Description: "The build command.", + Computed: true, + }, + "schedule": schema.StringAttribute{ + Description: "Function execution schedule in CRON format.", + Computed: true, + }, + "timeout": schema.Int64Attribute{ + Description: "Function execution timeout in seconds.", + Computed: true, + }, + "execute": schema.ListAttribute{ + Description: "Execution permissions.", + Computed: true, + ElementType: types.StringType, + }, + "events": schema.ListAttribute{ + Description: "Events that trigger the function.", + Computed: true, + ElementType: types.StringType, + }, + "scopes": schema.ListAttribute{ + Description: "Function scopes.", + Computed: true, + ElementType: types.StringType, + }, + "deployment_id": schema.StringAttribute{ + Description: "The active deployment ID.", + Computed: true, + }, + "created_at": schema.StringAttribute{ + Description: "The function creation timestamp.", + Computed: true, + }, + "updated_at": schema.StringAttribute{ + Description: "The function last update timestamp.", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "The Appwrite project ID. Defaults to the provider-level project_id.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (d *functionDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + clients, ok := req.ProviderData.(*common.AppwriteClients) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *common.AppwriteClients, got: %T", req.ProviderData), + ) + return + } + d.clients = clients +} + +func (d *functionDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config functionDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + projectID, err := common.ResolveProjectID(d.clients, config.ProjectID) + if err != nil { + resp.Diagnostics.AddError("Error resolving project ID", err.Error()) + return + } + functionsClient := appwrite.NewFunctions(d.clients.ClientForProject(projectID)) + + fn, err := functionsClient.Get(config.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error reading function", common.FormatError(err)) + return + } + + config.ProjectID = types.StringValue(projectID) + config.ID = types.StringValue(fn.Id) + config.Name = types.StringValue(fn.Name) + config.Runtime = types.StringValue(fn.Runtime) + config.Enabled = types.BoolValue(fn.Enabled) + config.Logging = types.BoolValue(fn.Logging) + config.Timeout = types.Int64Value(int64(fn.Timeout)) + config.DeploymentID = types.StringValue(fn.DeploymentId) + config.CreatedAt = types.StringValue(fn.CreatedAt) + config.UpdatedAt = types.StringValue(fn.UpdatedAt) + + if fn.Entrypoint != "" { + config.Entrypoint = types.StringValue(fn.Entrypoint) + } + if fn.Commands != "" { + config.Commands = types.StringValue(fn.Commands) + } + if fn.Schedule != "" { + config.Schedule = types.StringValue(fn.Schedule) + } + + execList, diags := types.ListValueFrom(ctx, types.StringType, fn.Execute) + resp.Diagnostics.Append(diags...) + config.Execute = execList + + eventsList, diags := types.ListValueFrom(ctx, types.StringType, fn.Events) + resp.Diagnostics.Append(diags...) + config.Events = eventsList + + scopesList, diags := types.ListValueFrom(ctx, types.StringType, fn.Scopes) + resp.Diagnostics.Append(diags...) + config.Scopes = scopesList + + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/internal/services/function/data_source_test.go b/internal/services/function/data_source_test.go new file mode 100644 index 0000000..500d6f7 --- /dev/null +++ b/internal/services/function/data_source_test.go @@ -0,0 +1,35 @@ +package function_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccFunctionDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_function" "test_ds" { + name = "DS Test Function" + runtime = "node-22" +} + +data "appwrite_function" "test" { + id = appwrite_function.test_ds.id +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.appwrite_function.test", "name", "DS Test Function"), + resource.TestCheckResourceAttr("data.appwrite_function.test", "runtime", "node-22"), + resource.TestCheckResourceAttr("data.appwrite_function.test", "enabled", "true"), + resource.TestCheckResourceAttrSet("data.appwrite_function.test", "created_at"), + ), + }, + }, + }) +} diff --git a/internal/services/function/deployment_resource.go b/internal/services/function/deployment_resource.go index 9807b90..59a4441 100644 --- a/internal/services/function/deployment_resource.go +++ b/internal/services/function/deployment_resource.go @@ -11,6 +11,7 @@ import ( "github.com/appwrite/sdk-for-go/v3/functions" "github.com/appwrite/sdk-for-go/v3/models" "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -19,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -84,6 +86,7 @@ func (r *deploymentResource) Schema(_ context.Context, _ resource.SchemaRequest, Description: `The deployment source type. Must be one of "code" or "template".`, Required: true, PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Validators: []validator.String{stringvalidator.OneOf("code", "template")}, }, "activate": schema.BoolAttribute{ Description: "Whether to activate this deployment after creation.", diff --git a/internal/services/function/deployment_resource_test.go b/internal/services/function/deployment_resource_test.go new file mode 100644 index 0000000..0ebc373 --- /dev/null +++ b/internal/services/function/deployment_resource_test.go @@ -0,0 +1,49 @@ +package function_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccFunctionDeploymentResource_template(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_function" "deploy_test" { + name = "Deployment Test" + runtime = "node-22" +} + +resource "appwrite_function_deployment" "test" { + function_id = appwrite_function.deploy_test.id + source_type = "template" + repository = "templates-for-node-22" + owner = "appwrite" + root_directory = "starter" + type = "branch" + reference = "main" + activate = true + wait_for_ready = true +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("appwrite_function_deployment.test", "id"), + resource.TestCheckResourceAttr("appwrite_function_deployment.test", "source_type", "template"), + resource.TestCheckResourceAttr("appwrite_function_deployment.test", "status", "ready"), + resource.TestCheckResourceAttrSet("appwrite_function_deployment.test", "created_at"), + ), + }, + { + ResourceName: "appwrite_function_deployment.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"source_type", "repository", "owner", "root_directory", "type", "reference", "activate", "wait_for_ready"}, + }, + }, + }) +} diff --git a/internal/services/function/resource.go b/internal/services/function/resource.go index 6576e71..6e8ca75 100644 --- a/internal/services/function/resource.go +++ b/internal/services/function/resource.go @@ -9,6 +9,7 @@ import ( "github.com/appwrite/sdk-for-go/v3/id" "github.com/appwrite/sdk-for-go/v3/models" "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -17,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -78,10 +80,12 @@ func (r *functionResource) Schema(_ context.Context, _ resource.SchemaRequest, r "name": schema.StringAttribute{ Description: "The function name.", Required: true, + Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, }, "runtime": schema.StringAttribute{ Description: "The function execution runtime (e.g. node-22, python-3.11, dart-3.5).", Required: true, + Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, }, "enabled": schema.BoolAttribute{ Description: "Whether the function is enabled. Defaults to true.", diff --git a/internal/services/index/resource.go b/internal/services/index/resource.go index b6504cf..bcb62d8 100644 --- a/internal/services/index/resource.go +++ b/internal/services/index/resource.go @@ -132,12 +132,12 @@ func (r *indexResource) Create(ctx context.Context, req resource.CreateRequest, opts = append(opts, tablesdbClient.WithCreateIndexOrders(orders)) } - databaseId := plan.DatabaseID.ValueString() - tableId := plan.TableID.ValueString() + databaseID := plan.DatabaseID.ValueString() + tableID := plan.TableID.ValueString() rawClient := r.clients.ClientForProject(projectID) for _, col := range columns { if err := common.WaitForColumnAvailable(ctx, func() (interface{}, error) { - return common.GetColumnRaw(rawClient, databaseId, tableId, col) + return common.GetColumnRaw(rawClient, databaseID, tableID, col) }, col); err != nil { resp.Diagnostics.AddError("Error waiting for columns", err.Error()) return @@ -150,8 +150,8 @@ func (r *indexResource) Create(ctx context.Context, req resource.CreateRequest, } idx, err := tablesdbClient.CreateIndex( - databaseId, - tableId, + databaseID, + tableID, indexKey, plan.Type.ValueString(), columns, diff --git a/internal/services/provider/resource.go b/internal/services/provider/resource.go index ff753d6..3171840 100644 --- a/internal/services/provider/resource.go +++ b/internal/services/provider/resource.go @@ -44,22 +44,22 @@ type providerResourceModel struct { Password types.String `tfsdk:"password"` Encryption types.String `tfsdk:"encryption"` AutoTLS types.Bool `tfsdk:"auto_tls"` - ApiKey types.String `tfsdk:"api_key"` - ApiSecret types.String `tfsdk:"api_secret"` + APIKey types.String `tfsdk:"api_key"` + APISecret types.String `tfsdk:"api_secret"` Domain types.String `tfsdk:"domain"` IsEuRegion types.Bool `tfsdk:"is_eu_region"` AccountSid types.String `tfsdk:"account_sid"` AuthToken types.String `tfsdk:"auth_token"` From types.String `tfsdk:"from"` - SenderId types.String `tfsdk:"sender_id"` + SenderID types.String `tfsdk:"sender_id"` AuthKey types.String `tfsdk:"auth_key"` - AuthKeyId types.String `tfsdk:"auth_key_id"` - TeamId types.String `tfsdk:"team_id"` - BundleId types.String `tfsdk:"bundle_id"` + AuthKeyID types.String `tfsdk:"auth_key_id"` + TeamID types.String `tfsdk:"team_id"` + BundleID types.String `tfsdk:"bundle_id"` Sandbox types.Bool `tfsdk:"sandbox"` ServiceAccount types.String `tfsdk:"service_account_json"` - CustomerId types.String `tfsdk:"customer_id"` - TemplateId types.String `tfsdk:"template_id"` + CustomerID types.String `tfsdk:"customer_id"` + TemplateID types.String `tfsdk:"template_id"` CreatedAt types.String `tfsdk:"created_at"` UpdatedAt types.String `tfsdk:"updated_at"` } @@ -267,7 +267,7 @@ func (r *providerResource) Create(ctx context.Context, req resource.CreateReques switch providerType { case "sendgrid": var opts []messaging.CreateSendgridProviderOption - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithCreateSendgridProviderApiKey(v.ValueString())) } if v := plan.FromEmail; !v.IsNull() { @@ -289,7 +289,7 @@ func (r *providerResource) Create(ctx context.Context, req resource.CreateReques case "mailgun": var opts []messaging.CreateMailgunProviderOption - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithCreateMailgunProviderApiKey(v.ValueString())) } if v := plan.Domain; !v.IsNull() { @@ -320,42 +320,42 @@ func (r *providerResource) Create(ctx context.Context, req resource.CreateReques resp.Diagnostics.AddError("Missing attribute", "host is required for smtp providers") return } - var opts []messaging.CreateSmtpProviderOption + var opts []messaging.CreateSMTPProviderOption if v := plan.Port; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderPort(int(v.ValueInt64()))) + opts = append(opts, messagingClient.WithCreateSMTPProviderPort(int(v.ValueInt64()))) } if v := plan.Username; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderUsername(v.ValueString())) + opts = append(opts, messagingClient.WithCreateSMTPProviderUsername(v.ValueString())) } if v := plan.Password; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderPassword(v.ValueString())) + opts = append(opts, messagingClient.WithCreateSMTPProviderPassword(v.ValueString())) } if v := plan.Encryption; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderEncryption(v.ValueString())) + opts = append(opts, messagingClient.WithCreateSMTPProviderEncryption(v.ValueString())) } if v := plan.AutoTLS; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderAutoTLS(v.ValueBool())) + opts = append(opts, messagingClient.WithCreateSMTPProviderAutoTLS(v.ValueBool())) } if v := plan.FromEmail; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderFromEmail(v.ValueString())) + opts = append(opts, messagingClient.WithCreateSMTPProviderFromEmail(v.ValueString())) } if v := plan.FromName; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderFromName(v.ValueString())) + opts = append(opts, messagingClient.WithCreateSMTPProviderFromName(v.ValueString())) } if v := plan.ReplyToEmail; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderReplyToEmail(v.ValueString())) + opts = append(opts, messagingClient.WithCreateSMTPProviderReplyToEmail(v.ValueString())) } if v := plan.ReplyToName; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateSmtpProviderReplyToName(v.ValueString())) + opts = append(opts, messagingClient.WithCreateSMTPProviderReplyToName(v.ValueString())) } if v := plan.Enabled; !v.IsNull() && !v.IsUnknown() { - opts = append(opts, messagingClient.WithCreateSmtpProviderEnabled(v.ValueBool())) + opts = append(opts, messagingClient.WithCreateSMTPProviderEnabled(v.ValueBool())) } - prov, err = messagingClient.CreateSmtpProvider(provID, name, plan.Host.ValueString(), opts...) + prov, err = messagingClient.CreateSMTPProvider(provID, name, plan.Host.ValueString(), opts...) case "resend": var opts []messaging.CreateResendProviderOption - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithCreateResendProviderApiKey(v.ValueString())) } if v := plan.FromEmail; !v.IsNull() { @@ -393,10 +393,10 @@ func (r *providerResource) Create(ctx context.Context, req resource.CreateReques case "vonage": var opts []messaging.CreateVonageProviderOption - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithCreateVonageProviderApiKey(v.ValueString())) } - if v := plan.ApiSecret; !v.IsNull() { + if v := plan.APISecret; !v.IsNull() { opts = append(opts, messagingClient.WithCreateVonageProviderApiSecret(v.ValueString())) } if v := plan.From; !v.IsNull() { @@ -409,13 +409,13 @@ func (r *providerResource) Create(ctx context.Context, req resource.CreateReques case "msg91": var opts []messaging.CreateMsg91ProviderOption - if v := plan.SenderId; !v.IsNull() { + if v := plan.SenderID; !v.IsNull() { opts = append(opts, messagingClient.WithCreateMsg91ProviderSenderId(v.ValueString())) } if v := plan.AuthKey; !v.IsNull() { opts = append(opts, messagingClient.WithCreateMsg91ProviderAuthKey(v.ValueString())) } - if v := plan.TemplateId; !v.IsNull() { + if v := plan.TemplateID; !v.IsNull() { opts = append(opts, messagingClient.WithCreateMsg91ProviderTemplateId(v.ValueString())) } if v := plan.Enabled; !v.IsNull() && !v.IsUnknown() { @@ -425,10 +425,10 @@ func (r *providerResource) Create(ctx context.Context, req resource.CreateReques case "telesign": var opts []messaging.CreateTelesignProviderOption - if v := plan.CustomerId; !v.IsNull() { + if v := plan.CustomerID; !v.IsNull() { opts = append(opts, messagingClient.WithCreateTelesignProviderCustomerId(v.ValueString())) } - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithCreateTelesignProviderApiKey(v.ValueString())) } if v := plan.From; !v.IsNull() { @@ -444,7 +444,7 @@ func (r *providerResource) Create(ctx context.Context, req resource.CreateReques if v := plan.Username; !v.IsNull() { opts = append(opts, messagingClient.WithCreateTextmagicProviderUsername(v.ValueString())) } - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithCreateTextmagicProviderApiKey(v.ValueString())) } if v := plan.From; !v.IsNull() { @@ -456,36 +456,36 @@ func (r *providerResource) Create(ctx context.Context, req resource.CreateReques prov, err = messagingClient.CreateTextmagicProvider(provID, name, opts...) case "apns": - var opts []messaging.CreateApnsProviderOption + var opts []messaging.CreateAPNSProviderOption if v := plan.AuthKey; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateApnsProviderAuthKey(v.ValueString())) + opts = append(opts, messagingClient.WithCreateAPNSProviderAuthKey(v.ValueString())) } - if v := plan.AuthKeyId; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateApnsProviderAuthKeyId(v.ValueString())) + if v := plan.AuthKeyID; !v.IsNull() { + opts = append(opts, messagingClient.WithCreateAPNSProviderAuthKeyId(v.ValueString())) } - if v := plan.TeamId; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateApnsProviderTeamId(v.ValueString())) + if v := plan.TeamID; !v.IsNull() { + opts = append(opts, messagingClient.WithCreateAPNSProviderTeamId(v.ValueString())) } - if v := plan.BundleId; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateApnsProviderBundleId(v.ValueString())) + if v := plan.BundleID; !v.IsNull() { + opts = append(opts, messagingClient.WithCreateAPNSProviderBundleId(v.ValueString())) } if v := plan.Sandbox; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateApnsProviderSandbox(v.ValueBool())) + opts = append(opts, messagingClient.WithCreateAPNSProviderSandbox(v.ValueBool())) } if v := plan.Enabled; !v.IsNull() && !v.IsUnknown() { - opts = append(opts, messagingClient.WithCreateApnsProviderEnabled(v.ValueBool())) + opts = append(opts, messagingClient.WithCreateAPNSProviderEnabled(v.ValueBool())) } - prov, err = messagingClient.CreateApnsProvider(provID, name, opts...) + prov, err = messagingClient.CreateAPNSProvider(provID, name, opts...) case "fcm": - var opts []messaging.CreateFcmProviderOption + var opts []messaging.CreateFCMProviderOption if v := plan.ServiceAccount; !v.IsNull() { - opts = append(opts, messagingClient.WithCreateFcmProviderServiceAccountJSON(v.ValueString())) + opts = append(opts, messagingClient.WithCreateFCMProviderServiceAccountJSON(v.ValueString())) } if v := plan.Enabled; !v.IsNull() && !v.IsUnknown() { - opts = append(opts, messagingClient.WithCreateFcmProviderEnabled(v.ValueBool())) + opts = append(opts, messagingClient.WithCreateFCMProviderEnabled(v.ValueBool())) } - prov, err = messagingClient.CreateFcmProvider(provID, name, opts...) + prov, err = messagingClient.CreateFCMProvider(provID, name, opts...) default: resp.Diagnostics.AddError("Unsupported provider type", fmt.Sprintf("Provider type %q is not supported.", providerType)) @@ -558,7 +558,7 @@ func (r *providerResource) Update(ctx context.Context, req resource.UpdateReques if v := plan.Name; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateSendgridProviderName(v.ValueString())) } - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateSendgridProviderApiKey(v.ValueString())) } if v := plan.FromEmail; !v.IsNull() { @@ -583,7 +583,7 @@ func (r *providerResource) Update(ctx context.Context, req resource.UpdateReques if v := plan.Name; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateMailgunProviderName(v.ValueString())) } - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateMailgunProviderApiKey(v.ValueString())) } if v := plan.Domain; !v.IsNull() { @@ -610,51 +610,51 @@ func (r *providerResource) Update(ctx context.Context, req resource.UpdateReques prov, err = messagingClient.UpdateMailgunProvider(id, opts...) case "smtp": - var opts []messaging.UpdateSmtpProviderOption + var opts []messaging.UpdateSMTPProviderOption if v := plan.Name; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderName(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderName(v.ValueString())) } if v := plan.Host; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderHost(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderHost(v.ValueString())) } if v := plan.Port; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderPort(int(v.ValueInt64()))) + opts = append(opts, messagingClient.WithUpdateSMTPProviderPort(int(v.ValueInt64()))) } if v := plan.Username; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderUsername(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderUsername(v.ValueString())) } if v := plan.Password; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderPassword(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderPassword(v.ValueString())) } if v := plan.Encryption; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderEncryption(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderEncryption(v.ValueString())) } if v := plan.AutoTLS; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderAutoTLS(v.ValueBool())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderAutoTLS(v.ValueBool())) } if v := plan.FromEmail; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderFromEmail(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderFromEmail(v.ValueString())) } if v := plan.FromName; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderFromName(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderFromName(v.ValueString())) } if v := plan.ReplyToEmail; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderReplyToEmail(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderReplyToEmail(v.ValueString())) } if v := plan.ReplyToName; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderReplyToName(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderReplyToName(v.ValueString())) } if v := plan.Enabled; !v.IsNull() && !v.IsUnknown() { - opts = append(opts, messagingClient.WithUpdateSmtpProviderEnabled(v.ValueBool())) + opts = append(opts, messagingClient.WithUpdateSMTPProviderEnabled(v.ValueBool())) } - prov, err = messagingClient.UpdateSmtpProvider(id, opts...) + prov, err = messagingClient.UpdateSMTPProvider(id, opts...) case "resend": var opts []messaging.UpdateResendProviderOption if v := plan.Name; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateResendProviderName(v.ValueString())) } - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateResendProviderApiKey(v.ValueString())) } if v := plan.FromEmail; !v.IsNull() { @@ -698,10 +698,10 @@ func (r *providerResource) Update(ctx context.Context, req resource.UpdateReques if v := plan.Name; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateVonageProviderName(v.ValueString())) } - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateVonageProviderApiKey(v.ValueString())) } - if v := plan.ApiSecret; !v.IsNull() { + if v := plan.APISecret; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateVonageProviderApiSecret(v.ValueString())) } if v := plan.From; !v.IsNull() { @@ -717,13 +717,13 @@ func (r *providerResource) Update(ctx context.Context, req resource.UpdateReques if v := plan.Name; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateMsg91ProviderName(v.ValueString())) } - if v := plan.SenderId; !v.IsNull() { + if v := plan.SenderID; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateMsg91ProviderSenderId(v.ValueString())) } if v := plan.AuthKey; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateMsg91ProviderAuthKey(v.ValueString())) } - if v := plan.TemplateId; !v.IsNull() { + if v := plan.TemplateID; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateMsg91ProviderTemplateId(v.ValueString())) } if v := plan.Enabled; !v.IsNull() && !v.IsUnknown() { @@ -736,10 +736,10 @@ func (r *providerResource) Update(ctx context.Context, req resource.UpdateReques if v := plan.Name; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateTelesignProviderName(v.ValueString())) } - if v := plan.CustomerId; !v.IsNull() { + if v := plan.CustomerID; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateTelesignProviderCustomerId(v.ValueString())) } - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateTelesignProviderApiKey(v.ValueString())) } if v := plan.From; !v.IsNull() { @@ -758,7 +758,7 @@ func (r *providerResource) Update(ctx context.Context, req resource.UpdateReques if v := plan.Username; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateTextmagicProviderUsername(v.ValueString())) } - if v := plan.ApiKey; !v.IsNull() { + if v := plan.APIKey; !v.IsNull() { opts = append(opts, messagingClient.WithUpdateTextmagicProviderApiKey(v.ValueString())) } if v := plan.From; !v.IsNull() { @@ -770,42 +770,42 @@ func (r *providerResource) Update(ctx context.Context, req resource.UpdateReques prov, err = messagingClient.UpdateTextmagicProvider(id, opts...) case "apns": - var opts []messaging.UpdateApnsProviderOption + var opts []messaging.UpdateAPNSProviderOption if v := plan.Name; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateApnsProviderName(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateAPNSProviderName(v.ValueString())) } if v := plan.AuthKey; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateApnsProviderAuthKey(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateAPNSProviderAuthKey(v.ValueString())) } - if v := plan.AuthKeyId; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateApnsProviderAuthKeyId(v.ValueString())) + if v := plan.AuthKeyID; !v.IsNull() { + opts = append(opts, messagingClient.WithUpdateAPNSProviderAuthKeyId(v.ValueString())) } - if v := plan.TeamId; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateApnsProviderTeamId(v.ValueString())) + if v := plan.TeamID; !v.IsNull() { + opts = append(opts, messagingClient.WithUpdateAPNSProviderTeamId(v.ValueString())) } - if v := plan.BundleId; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateApnsProviderBundleId(v.ValueString())) + if v := plan.BundleID; !v.IsNull() { + opts = append(opts, messagingClient.WithUpdateAPNSProviderBundleId(v.ValueString())) } if v := plan.Sandbox; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateApnsProviderSandbox(v.ValueBool())) + opts = append(opts, messagingClient.WithUpdateAPNSProviderSandbox(v.ValueBool())) } if v := plan.Enabled; !v.IsNull() && !v.IsUnknown() { - opts = append(opts, messagingClient.WithUpdateApnsProviderEnabled(v.ValueBool())) + opts = append(opts, messagingClient.WithUpdateAPNSProviderEnabled(v.ValueBool())) } - prov, err = messagingClient.UpdateApnsProvider(id, opts...) + prov, err = messagingClient.UpdateAPNSProvider(id, opts...) case "fcm": - var opts []messaging.UpdateFcmProviderOption + var opts []messaging.UpdateFCMProviderOption if v := plan.Name; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateFcmProviderName(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateFCMProviderName(v.ValueString())) } if v := plan.ServiceAccount; !v.IsNull() { - opts = append(opts, messagingClient.WithUpdateFcmProviderServiceAccountJSON(v.ValueString())) + opts = append(opts, messagingClient.WithUpdateFCMProviderServiceAccountJSON(v.ValueString())) } if v := plan.Enabled; !v.IsNull() && !v.IsUnknown() { - opts = append(opts, messagingClient.WithUpdateFcmProviderEnabled(v.ValueBool())) + opts = append(opts, messagingClient.WithUpdateFCMProviderEnabled(v.ValueBool())) } - prov, err = messagingClient.UpdateFcmProvider(id, opts...) + prov, err = messagingClient.UpdateFCMProvider(id, opts...) } if err != nil { diff --git a/internal/services/site/data_source.go b/internal/services/site/data_source.go new file mode 100644 index 0000000..671e54b --- /dev/null +++ b/internal/services/site/data_source.go @@ -0,0 +1,171 @@ +package site + +import ( + "context" + "fmt" + + "github.com/appwrite/sdk-for-go/v3/appwrite" + "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &siteDataSource{} + _ datasource.DataSourceWithConfigure = &siteDataSource{} +) + +type siteDataSource struct { + clients *common.AppwriteClients +} + +type siteDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Framework types.String `tfsdk:"framework"` + BuildRuntime types.String `tfsdk:"build_runtime"` + Enabled types.Bool `tfsdk:"enabled"` + Logging types.Bool `tfsdk:"logging"` + Timeout types.Int64 `tfsdk:"timeout"` + InstallCommand types.String `tfsdk:"install_command"` + BuildCommand types.String `tfsdk:"build_command"` + OutputDirectory types.String `tfsdk:"output_directory"` + DeploymentID types.String `tfsdk:"deployment_id"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + ProjectID types.String `tfsdk:"project_id"` +} + +func NewSiteDataSource() datasource.DataSource { + return &siteDataSource{} +} + +func (d *siteDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_site" +} + +func (d *siteDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Fetches an Appwrite site by ID.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The site ID.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The site name.", + Computed: true, + }, + "framework": schema.StringAttribute{ + Description: "The site framework.", + Computed: true, + }, + "build_runtime": schema.StringAttribute{ + Description: "The build runtime.", + Computed: true, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether the site is enabled.", + Computed: true, + }, + "logging": schema.BoolAttribute{ + Description: "Whether logging is enabled.", + Computed: true, + }, + "timeout": schema.Int64Attribute{ + Description: "Site timeout in seconds.", + Computed: true, + }, + "install_command": schema.StringAttribute{ + Description: "Custom install command.", + Computed: true, + }, + "build_command": schema.StringAttribute{ + Description: "Custom build command.", + Computed: true, + }, + "output_directory": schema.StringAttribute{ + Description: "Build output directory.", + Computed: true, + }, + "deployment_id": schema.StringAttribute{ + Description: "The active deployment ID.", + Computed: true, + }, + "created_at": schema.StringAttribute{ + Description: "The site creation timestamp.", + Computed: true, + }, + "updated_at": schema.StringAttribute{ + Description: "The site last update timestamp.", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "The Appwrite project ID. Defaults to the provider-level project_id.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (d *siteDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + clients, ok := req.ProviderData.(*common.AppwriteClients) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *common.AppwriteClients, got: %T", req.ProviderData), + ) + return + } + d.clients = clients +} + +func (d *siteDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config siteDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + projectID, err := common.ResolveProjectID(d.clients, config.ProjectID) + if err != nil { + resp.Diagnostics.AddError("Error resolving project ID", err.Error()) + return + } + sitesClient := appwrite.NewSites(d.clients.ClientForProject(projectID)) + + site, err := sitesClient.Get(config.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error reading site", common.FormatError(err)) + return + } + + config.ProjectID = types.StringValue(projectID) + config.ID = types.StringValue(site.Id) + config.Name = types.StringValue(site.Name) + config.Framework = types.StringValue(site.Framework) + config.BuildRuntime = types.StringValue(site.BuildRuntime) + config.Enabled = types.BoolValue(site.Enabled) + config.Logging = types.BoolValue(site.Logging) + config.Timeout = types.Int64Value(int64(site.Timeout)) + config.DeploymentID = types.StringValue(site.DeploymentId) + config.CreatedAt = types.StringValue(site.CreatedAt) + config.UpdatedAt = types.StringValue(site.UpdatedAt) + + if site.InstallCommand != "" { + config.InstallCommand = types.StringValue(site.InstallCommand) + } + if site.BuildCommand != "" { + config.BuildCommand = types.StringValue(site.BuildCommand) + } + if site.OutputDirectory != "" { + config.OutputDirectory = types.StringValue(site.OutputDirectory) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/internal/services/site/data_source_test.go b/internal/services/site/data_source_test.go new file mode 100644 index 0000000..94490bd --- /dev/null +++ b/internal/services/site/data_source_test.go @@ -0,0 +1,36 @@ +package site_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccSiteDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_site" "test_ds" { + name = "DS Test Site" + framework = "other" + build_runtime = "node-22" +} + +data "appwrite_site" "test" { + id = appwrite_site.test_ds.id +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.appwrite_site.test", "name", "DS Test Site"), + resource.TestCheckResourceAttr("data.appwrite_site.test", "framework", "other"), + resource.TestCheckResourceAttr("data.appwrite_site.test", "enabled", "true"), + resource.TestCheckResourceAttrSet("data.appwrite_site.test", "created_at"), + ), + }, + }, + }) +} diff --git a/internal/services/site/deployment_resource.go b/internal/services/site/deployment_resource.go index a2c5242..d6976ef 100644 --- a/internal/services/site/deployment_resource.go +++ b/internal/services/site/deployment_resource.go @@ -11,6 +11,7 @@ import ( "github.com/appwrite/sdk-for-go/v3/models" "github.com/appwrite/sdk-for-go/v3/sites" "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -19,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -85,6 +87,7 @@ func (r *deploymentResource) Schema(_ context.Context, _ resource.SchemaRequest, Description: `The deployment source type. Must be one of "code" or "template".`, Required: true, PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Validators: []validator.String{stringvalidator.OneOf("code", "template")}, }, "activate": schema.BoolAttribute{ Description: "Whether to activate this deployment after creation.", diff --git a/internal/services/site/deployment_resource_test.go b/internal/services/site/deployment_resource_test.go new file mode 100644 index 0000000..2e66331 --- /dev/null +++ b/internal/services/site/deployment_resource_test.go @@ -0,0 +1,50 @@ +package site_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccSiteDeploymentResource_template(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_site" "deploy_test" { + name = "Deployment Test Site" + framework = "other" + build_runtime = "node-22" +} + +resource "appwrite_site_deployment" "test" { + site_id = appwrite_site.deploy_test.id + source_type = "template" + repository = "templates-for-sites" + owner = "appwrite" + root_directory = "starter" + type = "branch" + reference = "main" + activate = true + wait_for_ready = true +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("appwrite_site_deployment.test", "id"), + resource.TestCheckResourceAttr("appwrite_site_deployment.test", "source_type", "template"), + resource.TestCheckResourceAttr("appwrite_site_deployment.test", "status", "ready"), + resource.TestCheckResourceAttrSet("appwrite_site_deployment.test", "created_at"), + ), + }, + { + ResourceName: "appwrite_site_deployment.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"source_type", "repository", "owner", "root_directory", "type", "reference", "activate", "wait_for_ready"}, + }, + }, + }) +} diff --git a/internal/services/table/resource_test.go b/internal/services/table/resource_test.go index f9df8ff..1309d6e 100644 --- a/internal/services/table/resource_test.go +++ b/internal/services/table/resource_test.go @@ -40,7 +40,7 @@ func TestAccTableResource_basic(t *testing.T) { }) } -func testAccTableConfig(databaseId, tableId, name string) string { +func testAccTableConfig(databaseID, tableID, name string) string { return fmt.Sprintf(` resource "appwrite_tablesdb" "test" { id = %q @@ -52,5 +52,5 @@ resource "appwrite_tablesdb_table" "test" { id = %q name = %q } -`, databaseId, tableId, name) +`, databaseID, tableID, name) } diff --git a/internal/services/team/data_source.go b/internal/services/team/data_source.go new file mode 100644 index 0000000..7059937 --- /dev/null +++ b/internal/services/team/data_source.go @@ -0,0 +1,110 @@ +package team + +import ( + "context" + "fmt" + + "github.com/appwrite/sdk-for-go/v3/appwrite" + "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &teamDataSource{} + _ datasource.DataSourceWithConfigure = &teamDataSource{} +) + +type teamDataSource struct { + clients *common.AppwriteClients +} + +type teamDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + ProjectID types.String `tfsdk:"project_id"` +} + +func NewTeamDataSource() datasource.DataSource { + return &teamDataSource{} +} + +func (d *teamDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_auth_team" +} + +func (d *teamDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Fetches an Appwrite team by ID.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The team ID.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The team name.", + Computed: true, + }, + "created_at": schema.StringAttribute{ + Description: "The team creation timestamp.", + Computed: true, + }, + "updated_at": schema.StringAttribute{ + Description: "The team last update timestamp.", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "The Appwrite project ID. Defaults to the provider-level project_id.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (d *teamDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + clients, ok := req.ProviderData.(*common.AppwriteClients) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *common.AppwriteClients, got: %T", req.ProviderData), + ) + return + } + d.clients = clients +} + +func (d *teamDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config teamDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + projectID, err := common.ResolveProjectID(d.clients, config.ProjectID) + if err != nil { + resp.Diagnostics.AddError("Error resolving project ID", err.Error()) + return + } + teamsClient := appwrite.NewTeams(d.clients.ClientForProject(projectID)) + + team, err := teamsClient.Get(config.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error reading team", common.FormatError(err)) + return + } + + config.ProjectID = types.StringValue(projectID) + config.ID = types.StringValue(team.Id) + config.Name = types.StringValue(team.Name) + config.CreatedAt = types.StringValue(team.CreatedAt) + config.UpdatedAt = types.StringValue(team.UpdatedAt) + + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/internal/services/team/data_source_test.go b/internal/services/team/data_source_test.go new file mode 100644 index 0000000..dc19615 --- /dev/null +++ b/internal/services/team/data_source_test.go @@ -0,0 +1,33 @@ +package team_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccTeamDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_auth_team" "test" { + id = "ds-test-team" + name = "DS Test Team" +} + +data "appwrite_auth_team" "test" { + id = appwrite_auth_team.test.id +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.appwrite_auth_team.test", "name", "DS Test Team"), + resource.TestCheckResourceAttrSet("data.appwrite_auth_team.test", "created_at"), + ), + }, + }, + }) +} diff --git a/internal/services/topic/data_source.go b/internal/services/topic/data_source.go new file mode 100644 index 0000000..03d1dc8 --- /dev/null +++ b/internal/services/topic/data_source.go @@ -0,0 +1,120 @@ +package topic + +import ( + "context" + "fmt" + + "github.com/appwrite/sdk-for-go/v3/appwrite" + "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &topicDataSource{} + _ datasource.DataSourceWithConfigure = &topicDataSource{} +) + +type topicDataSource struct { + clients *common.AppwriteClients +} + +type topicDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Subscribe types.List `tfsdk:"subscribe"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + ProjectID types.String `tfsdk:"project_id"` +} + +func NewTopicDataSource() datasource.DataSource { + return &topicDataSource{} +} + +func (d *topicDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_messaging_topic" +} + +func (d *topicDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Fetches an Appwrite messaging topic by ID.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The topic ID.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The topic name.", + Computed: true, + }, + "subscribe": schema.ListAttribute{ + Description: "Subscribe permissions.", + Computed: true, + ElementType: types.StringType, + }, + "created_at": schema.StringAttribute{ + Description: "The topic creation timestamp.", + Computed: true, + }, + "updated_at": schema.StringAttribute{ + Description: "The topic last update timestamp.", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "The Appwrite project ID. Defaults to the provider-level project_id.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (d *topicDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + clients, ok := req.ProviderData.(*common.AppwriteClients) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *common.AppwriteClients, got: %T", req.ProviderData), + ) + return + } + d.clients = clients +} + +func (d *topicDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config topicDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + projectID, err := common.ResolveProjectID(d.clients, config.ProjectID) + if err != nil { + resp.Diagnostics.AddError("Error resolving project ID", err.Error()) + return + } + messagingClient := appwrite.NewMessaging(d.clients.ClientForProject(projectID)) + + topic, err := messagingClient.GetTopic(config.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error reading topic", common.FormatError(err)) + return + } + + config.ProjectID = types.StringValue(projectID) + config.ID = types.StringValue(topic.Id) + config.Name = types.StringValue(topic.Name) + config.CreatedAt = types.StringValue(topic.CreatedAt) + config.UpdatedAt = types.StringValue(topic.UpdatedAt) + + subList, diags := types.ListValueFrom(ctx, types.StringType, topic.Subscribe) + resp.Diagnostics.Append(diags...) + config.Subscribe = subList + + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/internal/services/topic/data_source_test.go b/internal/services/topic/data_source_test.go new file mode 100644 index 0000000..b3bd50b --- /dev/null +++ b/internal/services/topic/data_source_test.go @@ -0,0 +1,33 @@ +package topic_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccTopicDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_messaging_topic" "test_ds" { + id = "ds-test-topic" + name = "DS Test Topic" +} + +data "appwrite_messaging_topic" "test" { + id = appwrite_messaging_topic.test_ds.id +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.appwrite_messaging_topic.test", "name", "DS Test Topic"), + resource.TestCheckResourceAttrSet("data.appwrite_messaging_topic.test", "created_at"), + ), + }, + }, + }) +} diff --git a/internal/services/user/data_source.go b/internal/services/user/data_source.go new file mode 100644 index 0000000..7cf5594 --- /dev/null +++ b/internal/services/user/data_source.go @@ -0,0 +1,157 @@ +package user + +import ( + "context" + "fmt" + + "github.com/appwrite/sdk-for-go/v3/appwrite" + "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &userDataSource{} + _ datasource.DataSourceWithConfigure = &userDataSource{} +) + +type userDataSource struct { + clients *common.AppwriteClients +} + +type userDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Email types.String `tfsdk:"email"` + Phone types.String `tfsdk:"phone"` + Status types.Bool `tfsdk:"status"` + Labels types.List `tfsdk:"labels"` + EmailVerification types.Bool `tfsdk:"email_verification"` + PhoneVerification types.Bool `tfsdk:"phone_verification"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + ProjectID types.String `tfsdk:"project_id"` +} + +func NewUserDataSource() datasource.DataSource { + return &userDataSource{} +} + +func (d *userDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_auth_user" +} + +func (d *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Fetches an Appwrite user by ID.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The user ID.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The user name.", + Computed: true, + }, + "email": schema.StringAttribute{ + Description: "The user email address.", + Computed: true, + }, + "phone": schema.StringAttribute{ + Description: "The user phone number.", + Computed: true, + }, + "status": schema.BoolAttribute{ + Description: "Whether the user account is enabled.", + Computed: true, + }, + "labels": schema.ListAttribute{ + Description: "User labels.", + Computed: true, + ElementType: types.StringType, + }, + "email_verification": schema.BoolAttribute{ + Description: "Whether the user email is verified.", + Computed: true, + }, + "phone_verification": schema.BoolAttribute{ + Description: "Whether the user phone is verified.", + Computed: true, + }, + "created_at": schema.StringAttribute{ + Description: "The user creation timestamp.", + Computed: true, + }, + "updated_at": schema.StringAttribute{ + Description: "The user last update timestamp.", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "The Appwrite project ID. Defaults to the provider-level project_id.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (d *userDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + clients, ok := req.ProviderData.(*common.AppwriteClients) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *common.AppwriteClients, got: %T", req.ProviderData), + ) + return + } + d.clients = clients +} + +func (d *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config userDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + projectID, err := common.ResolveProjectID(d.clients, config.ProjectID) + if err != nil { + resp.Diagnostics.AddError("Error resolving project ID", err.Error()) + return + } + usersClient := appwrite.NewUsers(d.clients.ClientForProject(projectID)) + + user, err := usersClient.Get(config.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error reading user", common.FormatError(err)) + return + } + + config.ProjectID = types.StringValue(projectID) + config.ID = types.StringValue(user.Id) + config.Status = types.BoolValue(user.Status) + config.EmailVerification = types.BoolValue(user.EmailVerification) + config.PhoneVerification = types.BoolValue(user.PhoneVerification) + config.CreatedAt = types.StringValue(user.CreatedAt) + config.UpdatedAt = types.StringValue(user.UpdatedAt) + + if user.Name != "" { + config.Name = types.StringValue(user.Name) + } + if user.Email != "" { + config.Email = types.StringValue(user.Email) + } + if user.Phone != "" { + config.Phone = types.StringValue(user.Phone) + } + + labelsList, diags := types.ListValueFrom(ctx, types.StringType, user.Labels) + resp.Diagnostics.Append(diags...) + config.Labels = labelsList + + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/internal/services/user/data_source_test.go b/internal/services/user/data_source_test.go new file mode 100644 index 0000000..098913b --- /dev/null +++ b/internal/services/user/data_source_test.go @@ -0,0 +1,36 @@ +package user_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccUserDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_auth_user" "test" { + email = "ds-test@example.com" + password = "password123456" + name = "DS Test User" +} + +data "appwrite_auth_user" "test" { + id = appwrite_auth_user.test.id +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.appwrite_auth_user.test", "name", "DS Test User"), + resource.TestCheckResourceAttr("data.appwrite_auth_user.test", "email", "ds-test@example.com"), + resource.TestCheckResourceAttr("data.appwrite_auth_user.test", "status", "true"), + resource.TestCheckResourceAttrSet("data.appwrite_auth_user.test", "created_at"), + ), + }, + }, + }) +} diff --git a/internal/services/user/resource.go b/internal/services/user/resource.go index 0bcbd54..04b2d2b 100644 --- a/internal/services/user/resource.go +++ b/internal/services/user/resource.go @@ -170,7 +170,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r userID = user.Id if !plan.Status.IsNull() && !plan.Status.IsUnknown() && !plan.Status.ValueBool() { - user, err = usersClient.UpdateStatus(userID, false) + _, err = usersClient.UpdateStatus(userID, false) if err != nil { resp.Diagnostics.AddError("Error setting user status", common.FormatError(err)) return @@ -182,21 +182,21 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r if resp.Diagnostics.HasError() { return } - user, err = usersClient.UpdateLabels(userID, labels) + _, err = usersClient.UpdateLabels(userID, labels) if err != nil { resp.Diagnostics.AddError("Error setting user labels", common.FormatError(err)) return } } if !plan.EmailVerification.IsNull() && !plan.EmailVerification.IsUnknown() && plan.EmailVerification.ValueBool() { - user, err = usersClient.UpdateEmailVerification(userID, true) + _, err = usersClient.UpdateEmailVerification(userID, true) if err != nil { resp.Diagnostics.AddError("Error setting email verification", common.FormatError(err)) return } } if !plan.PhoneVerification.IsNull() && !plan.PhoneVerification.IsUnknown() && plan.PhoneVerification.ValueBool() { - user, err = usersClient.UpdatePhoneVerification(userID, true) + _, err = usersClient.UpdatePhoneVerification(userID, true) if err != nil { resp.Diagnostics.AddError("Error setting phone verification", common.FormatError(err)) return diff --git a/internal/services/webhook/data_source.go b/internal/services/webhook/data_source.go new file mode 100644 index 0000000..0ade16c --- /dev/null +++ b/internal/services/webhook/data_source.go @@ -0,0 +1,138 @@ +package webhook + +import ( + "context" + "fmt" + + "github.com/appwrite/sdk-for-go/v3/appwrite" + "github.com/appwrite/terraform-provider-appwrite/internal/common" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &webhookDataSource{} + _ datasource.DataSourceWithConfigure = &webhookDataSource{} +) + +type webhookDataSource struct { + clients *common.AppwriteClients +} + +type webhookDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + URL types.String `tfsdk:"url"` + Events types.List `tfsdk:"events"` + Enabled types.Bool `tfsdk:"enabled"` + TLS types.Bool `tfsdk:"tls"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` + ProjectID types.String `tfsdk:"project_id"` +} + +func NewWebhookDataSource() datasource.DataSource { + return &webhookDataSource{} +} + +func (d *webhookDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_webhook" +} + +func (d *webhookDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Fetches an Appwrite webhook by ID.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "The webhook ID.", + Required: true, + }, + "name": schema.StringAttribute{ + Description: "The webhook name.", + Computed: true, + }, + "url": schema.StringAttribute{ + Description: "The webhook URL.", + Computed: true, + }, + "events": schema.ListAttribute{ + Description: "Events that trigger the webhook.", + Computed: true, + ElementType: types.StringType, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether the webhook is enabled.", + Computed: true, + }, + "tls": schema.BoolAttribute{ + Description: "Whether TLS verification is enabled.", + Computed: true, + }, + "created_at": schema.StringAttribute{ + Description: "The webhook creation timestamp.", + Computed: true, + }, + "updated_at": schema.StringAttribute{ + Description: "The webhook last update timestamp.", + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: "The Appwrite project ID. Defaults to the provider-level project_id.", + Optional: true, + Computed: true, + }, + }, + } +} + +func (d *webhookDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + clients, ok := req.ProviderData.(*common.AppwriteClients) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *common.AppwriteClients, got: %T", req.ProviderData), + ) + return + } + d.clients = clients +} + +func (d *webhookDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config webhookDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + projectID, err := common.ResolveProjectID(d.clients, config.ProjectID) + if err != nil { + resp.Diagnostics.AddError("Error resolving project ID", err.Error()) + return + } + webhooksClient := appwrite.NewWebhooks(d.clients.ClientForProject(projectID)) + + webhook, err := webhooksClient.Get(config.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error reading webhook", common.FormatError(err)) + return + } + + config.ProjectID = types.StringValue(projectID) + config.ID = types.StringValue(webhook.Id) + config.Name = types.StringValue(webhook.Name) + config.URL = types.StringValue(webhook.Url) + config.Enabled = types.BoolValue(webhook.Enabled) + config.TLS = types.BoolValue(webhook.Tls) + config.CreatedAt = types.StringValue(webhook.CreatedAt) + config.UpdatedAt = types.StringValue(webhook.UpdatedAt) + + eventsList, diags := types.ListValueFrom(ctx, types.StringType, webhook.Events) + resp.Diagnostics.Append(diags...) + config.Events = eventsList + + resp.Diagnostics.Append(resp.State.Set(ctx, &config)...) +} diff --git a/internal/services/webhook/data_source_test.go b/internal/services/webhook/data_source_test.go new file mode 100644 index 0000000..830496a --- /dev/null +++ b/internal/services/webhook/data_source_test.go @@ -0,0 +1,35 @@ +package webhook_test + +import ( + "testing" + + "github.com/appwrite/terraform-provider-appwrite/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccWebhookDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` +resource "appwrite_webhook" "test_ds" { + name = "DS Test Webhook" + url = "https://example.com/webhook" + events = ["databases.*.collections.*.documents.*.create"] +} + +data "appwrite_webhook" "test" { + id = appwrite_webhook.test_ds.id +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.appwrite_webhook.test", "name", "DS Test Webhook"), + resource.TestCheckResourceAttr("data.appwrite_webhook.test", "url", "https://example.com/webhook"), + resource.TestCheckResourceAttrSet("data.appwrite_webhook.test", "created_at"), + ), + }, + }, + }) +} diff --git a/internal/services/webhook/resource.go b/internal/services/webhook/resource.go index 87a4669..0039403 100644 --- a/internal/services/webhook/resource.go +++ b/internal/services/webhook/resource.go @@ -35,7 +35,7 @@ type webhookResourceModel struct { URL types.String `tfsdk:"url"` Events types.List `tfsdk:"events"` Enabled types.Bool `tfsdk:"enabled"` - Tls types.Bool `tfsdk:"tls"` + TLS types.Bool `tfsdk:"tls"` AuthUsername types.String `tfsdk:"auth_username"` AuthPassword types.String `tfsdk:"auth_password"` Secret types.String `tfsdk:"secret"` @@ -155,8 +155,8 @@ func (r *webhookResource) Create(ctx context.Context, req resource.CreateRequest if !plan.Enabled.IsNull() && !plan.Enabled.IsUnknown() { opts = append(opts, webhooksClient.WithCreateEnabled(plan.Enabled.ValueBool())) } - if !plan.Tls.IsNull() && !plan.Tls.IsUnknown() { - opts = append(opts, webhooksClient.WithCreateTls(plan.Tls.ValueBool())) + if !plan.TLS.IsNull() && !plan.TLS.IsUnknown() { + opts = append(opts, webhooksClient.WithCreateTls(plan.TLS.ValueBool())) } if !plan.AuthUsername.IsNull() { opts = append(opts, webhooksClient.WithCreateAuthUsername(plan.AuthUsername.ValueString())) @@ -229,8 +229,8 @@ func (r *webhookResource) Update(ctx context.Context, req resource.UpdateRequest if !plan.Enabled.IsNull() && !plan.Enabled.IsUnknown() { opts = append(opts, webhooksClient.WithUpdateEnabled(plan.Enabled.ValueBool())) } - if !plan.Tls.IsNull() && !plan.Tls.IsUnknown() { - opts = append(opts, webhooksClient.WithUpdateTls(plan.Tls.ValueBool())) + if !plan.TLS.IsNull() && !plan.TLS.IsUnknown() { + opts = append(opts, webhooksClient.WithUpdateTls(plan.TLS.ValueBool())) } if !plan.AuthUsername.IsNull() { opts = append(opts, webhooksClient.WithUpdateAuthUsername(plan.AuthUsername.ValueString())) @@ -279,7 +279,7 @@ func (r *webhookResource) mapToState(ctx context.Context, webhook *models.Webhoo model.Name = types.StringValue(webhook.Name) model.URL = types.StringValue(webhook.Url) model.Enabled = types.BoolValue(webhook.Enabled) - model.Tls = types.BoolValue(webhook.Tls) + model.TLS = types.BoolValue(webhook.Tls) model.Secret = types.StringValue(webhook.Secret) model.CreatedAt = types.StringValue(webhook.CreatedAt) model.UpdatedAt = types.StringValue(webhook.UpdatedAt) diff --git a/local/data.tf b/local/data.tf index 9858aa6..664ccb2 100644 --- a/local/data.tf +++ b/local/data.tf @@ -1,3 +1,31 @@ data "appwrite_tablesdb" "main" { id = appwrite_tablesdb.main.id } + +data "appwrite_storage_bucket" "uploads" { + id = appwrite_storage_bucket.uploads.id +} + +data "appwrite_auth_user" "john" { + id = appwrite_auth_user.john.id +} + +data "appwrite_auth_team" "engineering" { + id = appwrite_auth_team.engineering.id +} + +data "appwrite_function" "process_order" { + id = appwrite_function.process_order.id +} + +data "appwrite_site" "landing_page" { + id = appwrite_site.landing_page.id +} + +data "appwrite_messaging_topic" "announcements" { + id = appwrite_messaging_topic.announcements.id +} + +data "appwrite_webhook" "order_events" { + id = appwrite_webhook.order_events.id +} diff --git a/local/functions.tf b/local/functions.tf index 308e4e4..6601e7f 100644 --- a/local/functions.tf +++ b/local/functions.tf @@ -32,7 +32,7 @@ resource "appwrite_function_variable" "daily_report_smtp_host" { resource "appwrite_function_deployment" "process_order_code" { function_id = appwrite_function.process_order.id source_type = "code" - code_path = "./process-order.tar.gz" + code_path = "./files/process-order.tar.gz" code_hash = filesha256("./files/process-order.tar.gz") entrypoint = "index.js" commands = "npm install" diff --git a/local/output.tf b/local/output.tf index ca4184b..e415f18 100644 --- a/local/output.tf +++ b/local/output.tf @@ -1,3 +1,31 @@ output "database_name" { value = data.appwrite_tablesdb.main.name } + +output "bucket_name" { + value = data.appwrite_storage_bucket.uploads.name +} + +output "user_name" { + value = data.appwrite_auth_user.john.name +} + +output "team_name" { + value = data.appwrite_auth_team.engineering.name +} + +output "function_runtime" { + value = data.appwrite_function.process_order.runtime +} + +output "site_framework" { + value = data.appwrite_site.landing_page.framework +} + +output "topic_name" { + value = data.appwrite_messaging_topic.announcements.name +} + +output "webhook_url" { + value = data.appwrite_webhook.order_events.url +} diff --git a/local/sites.tf b/local/sites.tf index e9511cb..59995e5 100644 --- a/local/sites.tf +++ b/local/sites.tf @@ -28,7 +28,7 @@ resource "appwrite_site_variable" "landing_ga_id" { resource "appwrite_site_deployment" "landing_page_code" { site_id = appwrite_site.landing_page.id source_type = "code" - code_path = "./dist.tar.gz" + code_path = "./files/dist.tar.gz" code_hash = filesha256("./files/dist.tar.gz") activate = true } diff --git a/templates/data-sources/auth_team.md.tmpl b/templates/data-sources/auth_team.md.tmpl new file mode 100644 index 0000000..cfe0c7e --- /dev/null +++ b/templates/data-sources/auth_team.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Type}}: {{.Name}}" +subcategory: "Auth" +description: |- + {{.Description}} +--- + +# {{.Type}}: {{.Name}} + +{{.Description}} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/data-sources/auth_user.md.tmpl b/templates/data-sources/auth_user.md.tmpl new file mode 100644 index 0000000..cfe0c7e --- /dev/null +++ b/templates/data-sources/auth_user.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Type}}: {{.Name}}" +subcategory: "Auth" +description: |- + {{.Description}} +--- + +# {{.Type}}: {{.Name}} + +{{.Description}} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/data-sources/function.md.tmpl b/templates/data-sources/function.md.tmpl new file mode 100644 index 0000000..539e7d2 --- /dev/null +++ b/templates/data-sources/function.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Type}}: {{.Name}}" +subcategory: "Functions" +description: |- + {{.Description}} +--- + +# {{.Type}}: {{.Name}} + +{{.Description}} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/data-sources/messaging_topic.md.tmpl b/templates/data-sources/messaging_topic.md.tmpl new file mode 100644 index 0000000..3749fac --- /dev/null +++ b/templates/data-sources/messaging_topic.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Type}}: {{.Name}}" +subcategory: "Messaging" +description: |- + {{.Description}} +--- + +# {{.Type}}: {{.Name}} + +{{.Description}} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/data-sources/site.md.tmpl b/templates/data-sources/site.md.tmpl new file mode 100644 index 0000000..afc99d7 --- /dev/null +++ b/templates/data-sources/site.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Type}}: {{.Name}}" +subcategory: "Sites" +description: |- + {{.Description}} +--- + +# {{.Type}}: {{.Name}} + +{{.Description}} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/data-sources/storage_bucket.md.tmpl b/templates/data-sources/storage_bucket.md.tmpl new file mode 100644 index 0000000..eb8254c --- /dev/null +++ b/templates/data-sources/storage_bucket.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Type}}: {{.Name}}" +subcategory: "Storage" +description: |- + {{.Description}} +--- + +# {{.Type}}: {{.Name}} + +{{.Description}} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/data-sources/webhook.md.tmpl b/templates/data-sources/webhook.md.tmpl new file mode 100644 index 0000000..a26ab09 --- /dev/null +++ b/templates/data-sources/webhook.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Type}}: {{.Name}}" +subcategory: "Webhooks" +description: |- + {{.Description}} +--- + +# {{.Type}}: {{.Name}} + +{{.Description}} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index e05c6d7..9d7b344 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -85,3 +85,10 @@ The `project_id` can be set at the provider level as a default for all resources ## Data Sources - [appwrite_tablesdb](data-sources/tablesdb.md) - Fetch a database by ID +- [appwrite_storage_bucket](data-sources/storage_bucket.md) - Fetch a storage bucket by ID +- [appwrite_auth_user](data-sources/auth_user.md) - Fetch a user by ID +- [appwrite_auth_team](data-sources/auth_team.md) - Fetch a team by ID +- [appwrite_function](data-sources/function.md) - Fetch a function by ID +- [appwrite_site](data-sources/site.md) - Fetch a site by ID +- [appwrite_messaging_topic](data-sources/messaging_topic.md) - Fetch a messaging topic by ID +- [appwrite_webhook](data-sources/webhook.md) - Fetch a webhook by ID diff --git a/templates/resources/function_deployment.md.tmpl b/templates/resources/function_deployment.md.tmpl index 22448f0..9bdfa9b 100644 --- a/templates/resources/function_deployment.md.tmpl +++ b/templates/resources/function_deployment.md.tmpl @@ -9,63 +9,16 @@ description: |- {{.Description}} -Deployments are immutable - any change to input attributes will destroy the existing deployment and create a new one. Use the `wait_for_ready` attribute (default `true`) to wait for the build to complete before Terraform proceeds. - --> **Tip:** Use `code_hash = filesha256("./code.tar.gz")` to trigger a new deployment when your code file changes, even if the file path stays the same. - +{{ if .HasExample -}} ## Example Usage -### Code Upload - -```terraform -resource "appwrite_function_deployment" "process_order" { - function_id = appwrite_function.process_order.id - source_type = "code" - code_path = "./process-order.tar.gz" - code_hash = filesha256("./process-order.tar.gz") - entrypoint = "index.js" - commands = "npm install" - activate = true -} -``` - -### Template - -```terraform -resource "appwrite_function_deployment" "daily_report" { - function_id = appwrite_function.daily_report.id - source_type = "template" - repository = "templates" - owner = "appwrite" - root_directory = "node/starter" - type = "branch" - reference = "main" - activate = true -} -``` +{{ tffile .ExampleFile }} +{{- end }} {{ .SchemaMarkdown | trimspace }} -{{ if .HasImport -}} ## Import -Import uses the format `function_id/deployment_id`: +Import is supported using the following syntax: {{ codefile "shell" .ImportFile }} - -Using an import block (Terraform v1.5.0+): - -```hcl -import { - to = appwrite_function_deployment.example - id = "function-id/deployment-id" -} -``` -{{- end }} - -~> **NOTE:** Imported deployments will not have `source_type`, `code_path`, or template attributes in state since these are not returned by the API. The imported resource can be read and deleted but not recreated without specifying these attributes. - -## See Also - -- [appwrite_function](function.md) - Manage functions -- [appwrite_function_variable](function_variable.md) - Manage function environment variables diff --git a/templates/resources/site_deployment.md.tmpl b/templates/resources/site_deployment.md.tmpl index a9130bd..b08cb82 100644 --- a/templates/resources/site_deployment.md.tmpl +++ b/templates/resources/site_deployment.md.tmpl @@ -9,61 +9,16 @@ description: |- {{.Description}} -Deployments are immutable - any change to input attributes will destroy the existing deployment and create a new one. Use the `wait_for_ready` attribute (default `true`) to wait for the build to complete before Terraform proceeds. - --> **Tip:** Use `code_hash = filesha256("./dist.tar.gz")` to trigger a new deployment when your code file changes, even if the file path stays the same. - +{{ if .HasExample -}} ## Example Usage -### Code Upload - -```terraform -resource "appwrite_site_deployment" "landing_page" { - site_id = appwrite_site.landing_page.id - source_type = "code" - code_path = "./dist.tar.gz" - code_hash = filesha256("./dist.tar.gz") - activate = true -} -``` - -### Template - -```terraform -resource "appwrite_site_deployment" "dashboard" { - site_id = appwrite_site.dashboard.id - source_type = "template" - repository = "templates-for-sites" - owner = "appwrite" - root_directory = "nextjs/starter" - type = "branch" - reference = "main" - activate = true -} -``` +{{ tffile .ExampleFile }} +{{- end }} {{ .SchemaMarkdown | trimspace }} -{{ if .HasImport -}} ## Import -Import uses the format `site_id/deployment_id`: +Import is supported using the following syntax: {{ codefile "shell" .ImportFile }} - -Using an import block (Terraform v1.5.0+): - -```hcl -import { - to = appwrite_site_deployment.example - id = "site-id/deployment-id" -} -``` -{{- end }} - -~> **NOTE:** Imported deployments will not have `source_type`, `code_path`, or template attributes in state since these are not returned by the API. The imported resource can be read and deleted but not recreated without specifying these attributes. - -## See Also - -- [appwrite_site](site.md) - Manage sites -- [appwrite_site_variable](site_variable.md) - Manage site environment variables