├── examples ├── resources │ ├── qovery_helm │ │ ├── examples.md │ │ └── import.sh │ ├── qovery_container │ │ ├── examples.md │ │ └── import.sh │ ├── qovery_database │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_deployment │ │ ├── examples.md │ │ └── resource.tf │ ├── qovery_git_token │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_project │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_aws_credentials │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_helm_repository │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_labels_group │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_organization │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_annotations_group │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_container_registry │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_scaleway_credentials │ │ ├── examples.md │ │ ├── import.sh │ │ └── resource.tf │ ├── qovery_terraform_service │ │ ├── examples.md │ │ └── import.sh │ ├── qovery_job │ │ ├── import.sh │ │ └── examples.md │ ├── qovery_application │ │ ├── import.sh │ │ └── examples.md │ ├── qovery_cluster │ │ ├── import.sh │ │ └── examples.md │ ├── qovery_environment │ │ ├── import.sh │ │ ├── examples.md │ │ └── resource.tf │ └── qovery_deployment_stage │ │ ├── import.sh │ │ ├── examples.md │ │ └── resource.tf ├── data-sources │ ├── qovery_job │ │ └── data-source.tf │ ├── qovery_helm │ │ └── data-source.tf │ ├── qovery_database │ │ └── data-source.tf │ ├── qovery_project │ │ └── data-source.tf │ ├── qovery_container │ │ └── data-source.tf │ ├── qovery_application │ │ └── data-source.tf │ ├── qovery_environment │ │ └── data-source.tf │ ├── qovery_organization │ │ └── data-source.tf │ ├── qovery_container_registry │ │ └── data-source.tf │ ├── qovery_terraform_service │ │ └── data-source.tf │ ├── qovery_cluster │ │ └── data-source.tf │ ├── qovery_aws_credentials │ │ └── data-source.tf │ ├── qovery_labels_group │ │ └── data-source.tf │ ├── qovery_helm_repository │ │ └── data-source.tf │ └── qovery_annotations_group │ │ └── data-source.tf └── provider │ └── provider.tf ├── .tool-versions ├── terraform-registry-manifest.json ├── docs ├── data-sources │ ├── deployment.md │ ├── git_token.md │ ├── scaleway_credentials.md │ ├── deployment_stage.md │ ├── aws_credentials.md │ ├── annotations_group.md │ ├── organization.md │ ├── labels_group.md │ ├── container_registry.md │ ├── helm_repository.md │ └── database.md ├── index.md └── resources │ ├── annotations_group.md │ ├── organization.md │ ├── deployment.md │ ├── aws_credentials.md │ ├── labels_group.md │ ├── git_token.md │ ├── scaleway_credentials.md │ └── deployment_stage.md ├── internal ├── domain │ ├── service_type_enum.go │ ├── common │ │ └── errors.go │ ├── helm │ │ ├── helm_port.go │ │ ├── helm_port_protocol.go │ │ ├── helm_values_override.go │ │ ├── helm_source.go │ │ └── helm_repository.go │ ├── apierrors │ │ └── api_actions.go │ ├── organization │ │ ├── organization_service.go │ │ ├── organization_repository.go │ │ ├── organization_plan_test.go │ │ ├── organization_plan.go │ │ └── organization_test.go │ ├── execution_command │ │ ├── test_helper │ │ │ └── test_helper.go │ │ └── execution_command.go │ ├── labels_group │ │ ├── labels_group_service.go │ │ ├── labels_group_repository.go │ │ └── labels_group.go │ ├── annotations_group │ │ ├── annotations_group_service.go │ │ ├── annotations_group_repository.go │ │ └── annotations_group.go │ ├── gittoken │ │ ├── git_token_service.go │ │ └── git_token_type.go │ ├── deployment │ │ ├── deployment_repository.go │ │ └── deployment_service.go │ ├── helmRepository │ │ ├── helm_repository_repository.go │ │ ├── helm_repository_service.go │ │ └── helm_repository_kind.go │ ├── credentials │ │ ├── credentials_aws_repository.go │ │ ├── credentials_scaleway_repository.go │ │ ├── credentials_aws_service.go │ │ ├── credentials_scaleway_service.go │ │ ├── credentials_scaleway.go │ │ ├── credentials_aws.go │ │ └── credentials_test.go │ ├── status │ │ ├── status_state_test.go │ │ ├── service_deployment_status_test.go │ │ └── service_deployment_status.go │ ├── newdeployment │ │ ├── newdeployment_repository.go │ │ ├── newdeployment_service.go │ │ └── newdeployment_test.go │ ├── registry │ │ ├── registry_repository.go │ │ ├── registry_service.go │ │ ├── registry_kind_test.go │ │ └── registry_kind.go │ ├── port │ │ ├── port_protocol_test.go │ │ ├── port_protocol.go │ │ └── test_helper │ │ │ └── test_helper.go │ ├── storage │ │ ├── storage_type_test.go │ │ └── storage_type.go │ ├── environment │ │ ├── environment_mode_test.go │ │ └── environment_mode.go │ ├── variable │ │ ├── variable_scope_test.go │ │ ├── test_helper │ │ │ └── test_helper.go │ │ ├── variable_repository.go │ │ └── variable_scope.go │ ├── job │ │ ├── test_helper │ │ │ ├── job_source_test_helper.go │ │ │ ├── job_schedule_test_helper.go │ │ │ ├── job_scheduled_cron_test_helper.go │ │ │ └── job_test_helper.go │ │ └── job_schedule_cron.go │ ├── secret │ │ ├── test_helper │ │ │ └── test_helper.go │ │ ├── secret_repository.go │ │ └── secret_test.go │ ├── git_repository │ │ ├── git_repository.go │ │ └── test_helper │ │ │ └── test_helper.go │ ├── image │ │ ├── test_helper │ │ │ └── test_helper.go │ │ └── image.go │ ├── docker │ │ ├── test_helper │ │ │ └── test_helper.go │ │ └── docker.go │ ├── deploymentstage │ │ ├── deployment_stage_repository.go │ │ └── deployment_stage_service.go │ └── project │ │ └── project_repository.go └── infrastructure │ └── repositories │ └── qoveryapi │ ├── git_helpers.go │ ├── status_qoveryapi_models.go │ ├── project_qoveryapi_models.go │ ├── organization_qoveryapi_models.go │ ├── status_qoveryapi_models_test.go │ ├── labels_group_qoveryapi_model.go │ ├── helm_repository_qoveryapi_models.go │ └── container_registry_qoveryapi_models.go ├── tools └── tools.go ├── qovery ├── resource_execution_command_model.go ├── descriptions │ ├── bool_default_description.go │ ├── string_default_description.go │ ├── int64_min_description.go │ ├── int64_min_max_description.go │ ├── string_enum_description.go │ └── map_string_array_enum_description.go ├── resource_image_model.go ├── resource_docker_model.go ├── git_helpers.go ├── resource_organization_model.go ├── resource_git_repository_model.go ├── data_source_cluster_test.go ├── data_source_organization_test.go ├── data_source_aws_credentials_test.go ├── data_source_scaleway_credentials_test.go ├── resource_git_token_models.go ├── data_source_container_registry_test.go ├── data_source_helm_repository_test.go ├── data_source_annotations_group_test.go ├── validators │ ├── int64_min_validator.go │ └── int64_min_max_validator.go ├── data_source_project_test.go ├── data_source_labels_group_test.go ├── data_source_environment_test.go └── resource_aws_credentials_model.go ├── templates ├── data-sources.md.tmpl ├── index.md.tmpl └── resources.md.tmpl ├── client ├── apierrors │ ├── api_actions.go │ └── api_error.go ├── environment_status.go ├── client.go ├── custom_domains.go ├── secrets.go ├── environment_variables.go ├── retry_helper.go ├── cluster_status.go ├── application_secrets.go ├── application_custom_domains.go └── database_status.go ├── .github ├── workflows │ ├── matrix_includes.json │ └── release.yml └── dependabot.yml ├── .golangci.yml ├── scripts └── fetch_instance_types.sh ├── .env.example ├── .gitignore ├── main.go └── .goreleaser.yml /examples/resources/qovery_helm/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_container/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_database/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_deployment/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_git_token/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_project/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_aws_credentials/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_helm_repository/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_labels_group/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_organization/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_annotations_group/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_container_registry/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_scaleway_credentials/examples.md: -------------------------------------------------------------------------------- 1 | empty -------------------------------------------------------------------------------- /examples/resources/qovery_terraform_service/examples.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.25.4 2 | task 3.45.5 3 | golangci-lint 1.64.8 4 | -------------------------------------------------------------------------------- /examples/resources/qovery_helm/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_helm.my_helm "" 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_job/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_job.my_job "" 2 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_job/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_job" "my_job" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/data-sources/qovery_helm/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_helm" "my_helm" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/resources/qovery_project/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_project.my_project "" 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_container/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_container.my_container "" 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_database/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_database.my_database "" 2 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_database/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_database" "my_database" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/data-sources/qovery_project/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_project" "my_project" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/resources/qovery_application/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_application.my_application "" 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_cluster/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_cluster.my_cluster "," 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_environment/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_environment.my_environment "" 2 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_container/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_container" "my_container" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/resources/qovery_organization/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_organization.my_organization "" 2 | -------------------------------------------------------------------------------- /terraform-registry-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "metadata": { 4 | "protocol_versions": ["6.0"] 5 | } 6 | } -------------------------------------------------------------------------------- /examples/resources/qovery_git_token/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_git_token.my_git_token "," 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_labels_group/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_labels_group.my_qovery_labels_group "" 2 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_application/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_application" "my_application" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/data-sources/qovery_environment/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_environment" "my_environment" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/data-sources/qovery_organization/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_organization" "my_organization" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/resources/qovery_terraform_service/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_terraform_service.my_terraform_service "" 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_annotations_group/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_annotations_group.my_qovery_annotations_group "" 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_aws_credentials/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_aws_credentials.my_aws_creds "," 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_helm_repository/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_helm_repository.my_helm_repository "," 2 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_container_registry/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_container_registry" "my_container_registry" { 2 | id = "" 3 | } -------------------------------------------------------------------------------- /examples/resources/qovery_deployment_stage/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_deployment_stage.my_deployment_stage "," 2 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_terraform_service/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_terraform_service" "my_terraform_service" { 2 | id = "" 3 | } 4 | -------------------------------------------------------------------------------- /examples/resources/qovery_container_registry/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_container_registry.my_container_registry "," 2 | -------------------------------------------------------------------------------- /examples/resources/qovery_scaleway_credentials/import.sh: -------------------------------------------------------------------------------- 1 | terraform import qovery_scaleway_credentials.my_scaleway_creds "," 2 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_cluster/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_cluster" "my_cluster" { 2 | id = "" 3 | organization_id = "" 4 | } -------------------------------------------------------------------------------- /docs/data-sources/deployment.md: -------------------------------------------------------------------------------- 1 | # qovery_deployment (Data Source) 2 | 3 | Provides a Qovery deployment resource. This is used to trigger a service deployment at demand. 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/data-sources/git_token.md: -------------------------------------------------------------------------------- 1 | # qovery_git_token (Data Source) 2 | 3 | Provides a Qovery git token resource. This can be used to create and manage Qovery git token. 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/data-sources/scaleway_credentials.md: -------------------------------------------------------------------------------- 1 | # qovery_scaleway_credentials (Data Source) 2 | 3 | Use this data source to retrieve information about an existing Scaleway credentials. 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_aws_credentials/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_aws_credentials" "my_aws_creds" { 2 | id = "" 3 | organization_id = "" 4 | } -------------------------------------------------------------------------------- /examples/resources/qovery_deployment_stage/examples.md: -------------------------------------------------------------------------------- 1 | * [Deploy services with a specific order](https://github.com/Qovery/terraform-examples/tree/main/examples/deploy-services-with-a-specific-order) -------------------------------------------------------------------------------- /examples/data-sources/qovery_labels_group/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_labels_group" "my_labels_group" { 2 | id = "" 3 | organization_id = "" 4 | } 5 | -------------------------------------------------------------------------------- /docs/data-sources/deployment_stage.md: -------------------------------------------------------------------------------- 1 | # qovery_deployment_stage (Data Source) 2 | 3 | Provides a Qovery deployment stage resource. This can be used to create and manage Qovery deployment stages. 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_helm_repository/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_helm_repository" "my_helm_repository" { 2 | id = "" 3 | organization_id = "" 4 | } 5 | -------------------------------------------------------------------------------- /internal/domain/service_type_enum.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | const ( 4 | APPLICATION int = 0 5 | CONTAINER int = 1 6 | JOB int = 2 7 | HELM int = 3 8 | TERRAFORM int = 4 9 | ) 10 | -------------------------------------------------------------------------------- /examples/resources/qovery_application/examples.md: -------------------------------------------------------------------------------- 1 | * [Deploy an Application and Database within 3 environments](https://github.com/Qovery/terraform-examples/tree/main/examples/deploy-an-application-within-3-environments) -------------------------------------------------------------------------------- /examples/resources/qovery_cluster/examples.md: -------------------------------------------------------------------------------- 1 | * [Deploy an Application and Database within 3 environments](https://github.com/Qovery/terraform-examples/tree/main/examples/deploy-an-application-within-3-environments) -------------------------------------------------------------------------------- /examples/resources/qovery_environment/examples.md: -------------------------------------------------------------------------------- 1 | * [Deploy an Application and Database within 3 environments](https://github.com/Qovery/terraform-examples/tree/main/examples/deploy-an-application-within-3-environments) -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | import ( 7 | // Documentation generation 8 | _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" 9 | ) 10 | -------------------------------------------------------------------------------- /examples/data-sources/qovery_annotations_group/data-source.tf: -------------------------------------------------------------------------------- 1 | data "qovery_annotations_group" "my_annotations_group" { 2 | id = "" 3 | organization_id = "" 4 | } 5 | -------------------------------------------------------------------------------- /examples/resources/qovery_organization/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_organization" "my_organization" { 2 | # Required 3 | name = "MyOrganization" 4 | plan = "FREE" 5 | 6 | # Optional 7 | description = "My organization description" 8 | } -------------------------------------------------------------------------------- /examples/resources/qovery_job/examples.md: -------------------------------------------------------------------------------- 1 | * [Deploy a cron job](https://github.com/Qovery/terraform-examples/tree/main/examples/deploy-a-cron-job) 2 | * [Deploy a lifecycle job](https://github.com/Qovery/terraform-examples/tree/main/examples/deploy-a-lifecycle-job) -------------------------------------------------------------------------------- /qovery/resource_execution_command_model.go: -------------------------------------------------------------------------------- 1 | package qovery 2 | 3 | import "github.com/hashicorp/terraform-plugin-framework/types" 4 | 5 | type ExecutionCommand struct { 6 | Entrypoint types.String `tfsdk:"entrypoint"` 7 | Arguments []types.String `tfsdk:"arguments"` 8 | } 9 | -------------------------------------------------------------------------------- /internal/domain/common/errors.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | var ( 8 | // ErrInvalidQoveryClient is the error return if the *qovery.Client is nil or invalid. 9 | ErrInvalidQoveryClient = errors.New("invalid qovery client") 10 | ) 11 | -------------------------------------------------------------------------------- /qovery/descriptions/bool_default_description.go: -------------------------------------------------------------------------------- 1 | package descriptions 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func NewBoolDefaultDescription(description string, defaultValue bool) string { 8 | return fmt.Sprintf( 9 | "%s\n\t- Default: `%t`.", 10 | description, 11 | defaultValue, 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /qovery/descriptions/string_default_description.go: -------------------------------------------------------------------------------- 1 | package descriptions 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func NewStringDefaultDescription(description string, defaultValue string) string { 8 | return fmt.Sprintf( 9 | "%s\n\t- Default: `%s`.", 10 | description, 11 | defaultValue, 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /examples/provider/provider.tf: -------------------------------------------------------------------------------- 1 | # Terraform 1.0.3+ uses the Terraform Registry: 2 | 3 | terraform { 4 | required_providers { 5 | qovery = { 6 | source = "qovery/qovery" 7 | } 8 | } 9 | } 10 | 11 | # Configure the Qovery provider 12 | provider "qovery" { 13 | token = "" 14 | } 15 | -------------------------------------------------------------------------------- /internal/domain/helm/helm_port.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | const ( 4 | DefaultProtocol = ProtocolHttp 5 | ) 6 | 7 | type Port struct { 8 | Name string 9 | InternalPort int32 10 | ExternalPort *int32 11 | ServiceName string 12 | Namespace *string 13 | Protocol Protocol 14 | IsDefault bool 15 | } 16 | -------------------------------------------------------------------------------- /examples/resources/qovery_annotations_group/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_annotations_group" "annotations_group1" { 2 | organization_id = qovery_organization.my_organization.id 3 | name = "MyAnnotationsGroup" 4 | annotations = { 5 | "key1" = "value1" 6 | "key2" = "value2" 7 | } 8 | scopes = ["PODS", "DEPLOYMENTS"] 9 | } -------------------------------------------------------------------------------- /templates/data-sources.md.tmpl: -------------------------------------------------------------------------------- 1 | # {{ .Name }} ({{ .Type }}) 2 | 3 | {{ printf "%s" .Description }} 4 | {{ if .HasExample -}} 5 | ## Example Usage 6 | {{ tffile .ExampleFile }} 7 | 8 | {{ .SchemaMarkdown | trimspace }} 9 | {{- end }} 10 | 11 | {{ if .HasImport -}} 12 | ## Import 13 | {{ codefile "shell" .ImportFile }} 14 | {{- end -}} -------------------------------------------------------------------------------- /examples/resources/qovery_aws_credentials/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_aws_credentials" "my_aws_creds" { 2 | # Required 3 | organization_id = qovery_organization.my_organization.id 4 | name = "my_aws_creds" 5 | access_key_id = "" 6 | secret_access_key = "" 7 | 8 | depends_on = [ 9 | qovery_organization.my_organization 10 | ] 11 | } -------------------------------------------------------------------------------- /templates/index.md.tmpl: -------------------------------------------------------------------------------- 1 | # Qovery Provider 2 | 3 | The [Qovery](https://www.qovery.com/) provider is used to interact with the resources supported by Qovery. 4 | The provider needs to be configured with the proper credentials before it can be used. 5 | It requires terraform 1.0.3 or later. 6 | 7 | 8 | ## Example Usage 9 | 10 | {{tffile "examples/provider/provider.tf"}} 11 | 12 | {{ .SchemaMarkdown | trimspace }} -------------------------------------------------------------------------------- /examples/resources/qovery_deployment/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_deployment" "my_deployment" { 2 | # Required 3 | environment_id = qovery_environment.my_environment.id 4 | desired_state = "RUNNING" 5 | version = "random_uuid_to_force_retrigger_terraform_apply" 6 | 7 | depends_on = [ 8 | qovery_application.my_application, 9 | qovery_database.my_database, 10 | qovery_container.my_container, 11 | ] 12 | } -------------------------------------------------------------------------------- /examples/resources/qovery_git_token/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_git_token" "my_git_token" { 2 | # Required 3 | organization_id = qovery_organization.my_organization.id 4 | name = "my-git-token" 5 | type = "GITHUB" 6 | token = "my-git-provider-token" 7 | 8 | # Optional 9 | description = "Github token" 10 | 11 | # Only necessary for BITBUCKET git tokens 12 | bitbucket_workspace = "workspace-bitbucket" 13 | } -------------------------------------------------------------------------------- /qovery/descriptions/int64_min_description.go: -------------------------------------------------------------------------------- 1 | package descriptions 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func NewInt64MinDescription(description string, min int64, defaultValue *int64) string { 8 | desc := fmt.Sprintf( 9 | "%s\n\t- Must be: `>= %d`.", 10 | description, 11 | min, 12 | ) 13 | 14 | if defaultValue != nil { 15 | desc += fmt.Sprintf( 16 | "\n\t- Default: `%d`.", 17 | *defaultValue, 18 | ) 19 | } 20 | 21 | return desc 22 | } 23 | -------------------------------------------------------------------------------- /client/apierrors/api_actions.go: -------------------------------------------------------------------------------- 1 | package apierrors 2 | 3 | type APIAction string 4 | 5 | const ( 6 | APIActionCreate APIAction = "create" 7 | APIActionRead APIAction = "read" 8 | APIActionUpdate APIAction = "update" 9 | APIActionDelete APIAction = "delete" 10 | APIActionDeploy APIAction = "deploy" 11 | APIActionStop APIAction = "stop" 12 | APIActionRedeploy APIAction = "redeploy" 13 | APIActionNotSupported APIAction = "not supported" 14 | ) 15 | -------------------------------------------------------------------------------- /examples/resources/qovery_deployment_stage/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_deployment_stage" "my_deployment_stage" { 2 | # Required 3 | environment_id = qovery_environment.my_environment.id 4 | name = "MyDeploymentStage" 5 | 6 | # Optional 7 | description = "" 8 | is_after = qovery_deployment_stage.first_deployment_stage.id 9 | is_before = qovery_deployment_stage.third_deployment_stage.id 10 | 11 | depends_on = [ 12 | qovery_environment.my_environment 13 | ] 14 | } -------------------------------------------------------------------------------- /examples/resources/qovery_scaleway_credentials/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_scaleway_credentials" "my_scaleway_creds" { 2 | # Required 3 | organization_id = qovery_organization.my_organization.id 4 | name = "my_scaleway_creds" 5 | scaleway_access_key = "" 6 | scaleway_secret_key = "" 7 | scaleway_project_id = "" 8 | 9 | depends_on = [ 10 | qovery_organization.my_organization 11 | ] 12 | } -------------------------------------------------------------------------------- /qovery/descriptions/int64_min_max_description.go: -------------------------------------------------------------------------------- 1 | package descriptions 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func NewInt64MinMaxDescription(description string, min int64, max int64, defaultValue *int64) string { 8 | desc := fmt.Sprintf( 9 | "%s\n\t- Must be: `>= %d` and `<= %d`.", 10 | description, 11 | min, 12 | max, 13 | ) 14 | 15 | if defaultValue != nil { 16 | desc += fmt.Sprintf( 17 | "\n\t- Default: `%d`.", 18 | *defaultValue, 19 | ) 20 | } 21 | 22 | return desc 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/matrix_includes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "terraform": "1.8.*'", 4 | "runOnBranch": "always" 5 | }, 6 | { 7 | "terraform": "1.9.*'", 8 | "runOnBranch": "always" 9 | }, 10 | { 11 | "terraform": "1.10.*'", 12 | "runOnBranch": "always" 13 | }, 14 | { 15 | "terraform": "1.11.*'", 16 | "runOnBranch": "always" 17 | }, 18 | { 19 | "terraform": "1.12.*'", 20 | "runOnBranch": "always" 21 | }, 22 | { 23 | "terraform": "1.13.*'", 24 | "runOnBranch": "always" 25 | } 26 | ] -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See GitHub's documentation for more information on this file: 2 | # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates 3 | version: 2 4 | updates: 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | - package-ecosystem: "gomod" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | rebase-strategy: auto 14 | open-pull-requests-limit: 20 -------------------------------------------------------------------------------- /examples/resources/qovery_labels_group/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_labels_group" "labels_group1" { 2 | organization_id = qovery_organization.my_organization.id 3 | name = "MyLabelsGroup" 4 | labels = [ 5 | { 6 | key = "key1" 7 | value = "value1" 8 | propagate_to_cloud_provider = false 9 | }, 10 | { 11 | key = "key2" 12 | value = "value2" 13 | propagate_to_cloud_provider = true 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /qovery/descriptions/string_enum_description.go: -------------------------------------------------------------------------------- 1 | package descriptions 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | func NewStringEnumDescription(description string, enum []string, defaultValue *string) string { 10 | sort.Strings(enum) 11 | 12 | desc := fmt.Sprintf( 13 | "%s\n\t- Can be: `%s`.", 14 | description, 15 | strings.Join(enum, "`, `"), 16 | ) 17 | 18 | if defaultValue != nil { 19 | desc += fmt.Sprintf( 20 | "\n\t- Default: `%s`.", 21 | *defaultValue, 22 | ) 23 | } 24 | 25 | return desc 26 | } 27 | -------------------------------------------------------------------------------- /examples/resources/qovery_container_registry/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_container_registry" "my_container_registry" { 2 | # Required 3 | organization_id = qovery_organization.my_organization.id 4 | name = "my_aws_creds" 5 | kind = "DOCKER_HUB" 6 | url = "https://docker.io" 7 | config = { 8 | username = "" 9 | password = "" 10 | } 11 | 12 | # Optional 13 | description = "My Docker Hub Registry" 14 | 15 | depends_on = [ 16 | qovery_organization.my_organization 17 | ] 18 | } -------------------------------------------------------------------------------- /qovery/resource_image_model.go: -------------------------------------------------------------------------------- 1 | package qovery 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/types" 5 | "github.com/qovery/terraform-provider-qovery/internal/domain/image" 6 | ) 7 | 8 | type Image struct { 9 | RegistryID types.String `tfsdk:"registry_id"` 10 | Name types.String `tfsdk:"name"` 11 | Tag types.String `tfsdk:"tag"` 12 | } 13 | 14 | func (i Image) toUpsertRequest() *image.Image { 15 | return &image.Image{ 16 | RegistryID: ToString(i.RegistryID), 17 | Name: ToString(i.Name), 18 | Tag: ToString(i.Tag), 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /internal/domain/apierrors/api_actions.go: -------------------------------------------------------------------------------- 1 | package apierrors 2 | 3 | // APIAction is an enum that contains every type of actions done using the api. 4 | // This is used to generate a detailed error message displayed by terraform when the api return an error. 5 | type APIAction string 6 | 7 | const ( 8 | APIActionCreate APIAction = "create" 9 | APIActionRead APIAction = "read" 10 | APIActionUpdate APIAction = "update" 11 | APIActionDelete APIAction = "delete" 12 | APIActionDeploy APIAction = "deploy" 13 | APIActionStop APIAction = "stop" 14 | APIActionRedeploy APIAction = "redeploy" 15 | ) 16 | -------------------------------------------------------------------------------- /examples/resources/qovery_helm_repository/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_helm_repository" "my_helm_repository" { 2 | # Required 3 | organization_id = qovery_organization.my_organization.id 4 | name = "my_helm_repository" 5 | kind = "OCI_DOCKER_HUB" 6 | url = "https://docker.io" 7 | skip_tls_verification = false 8 | 9 | # Optional 10 | description = "My Helm repository" 11 | config = { 12 | username = "" 13 | password = "" 14 | } 15 | 16 | 17 | depends_on = [ 18 | qovery_organization.my_organization 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /internal/domain/organization/organization_service.go: -------------------------------------------------------------------------------- 1 | package organization 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | ErrFailedToGetOrganization = errors.New("failed to get organization") 11 | ErrFailedToUpdateOrganization = errors.New("failed to update organization") 12 | ) 13 | 14 | // Service represents the interface to implement to handle the domain logic of an Organization. 15 | type Service interface { 16 | Get(ctx context.Context, organizationID string) (*Organization, error) 17 | Update(ctx context.Context, organizationID string, request UpdateRequest) (*Organization, error) 18 | } 19 | -------------------------------------------------------------------------------- /client/environment_status.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | 8 | "github.com/qovery/terraform-provider-qovery/client/apierrors" 9 | ) 10 | 11 | func (c *Client) getEnvironmentStatus(ctx context.Context, environmentID string) (*qovery.EnvironmentStatus, *apierrors.APIError) { 12 | status, res, err := c.api.EnvironmentMainCallsAPI. 13 | GetEnvironmentStatus(ctx, environmentID). 14 | Execute() 15 | if err != nil || res.StatusCode >= 400 { 16 | return nil, apierrors.NewReadError(apierrors.APIResourceEnvironmentStatus, environmentID, res, err) 17 | } 18 | return status, nil 19 | } 20 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 60m 3 | 4 | issues: 5 | max-per-linter: 0 6 | max-same-issues: 0 7 | 8 | linters: 9 | disable-all: true 10 | enable: 11 | - asciicheck 12 | - errcheck 13 | - gocritic 14 | - gofmt 15 | - goimports 16 | - gosimple 17 | - govet 18 | - ineffassign 19 | - nakedret 20 | - misspell 21 | - staticcheck 22 | - typecheck 23 | - unused 24 | - unconvert 25 | - unparam 26 | # - whitespace # Disabled for performance reasons - Ignores cache and takes 12+ minutes to run on the repo for _any_ change 27 | 28 | linters-settings: 29 | nakedret: 30 | max-func-lines: 40 31 | -------------------------------------------------------------------------------- /docs/data-sources/aws_credentials.md: -------------------------------------------------------------------------------- 1 | # qovery_aws_credentials (Data Source) 2 | 3 | Provides a Qovery AWS credentials resource. This can be used to create and manage Qovery AWS credentials. 4 | ## Example Usage 5 | ```terraform 6 | data "qovery_aws_credentials" "my_aws_creds" { 7 | id = "" 8 | organization_id = "" 9 | } 10 | ``` 11 | 12 | 13 | ## Schema 14 | 15 | ### Required 16 | 17 | - `id` (String) Id of the AWS credentials. 18 | - `organization_id` (String) Id of the organization. 19 | 20 | ### Read-Only 21 | 22 | - `name` (String) Name of the aws credentials. 23 | 24 | -------------------------------------------------------------------------------- /internal/domain/organization/organization_repository.go: -------------------------------------------------------------------------------- 1 | package organization 2 | 3 | //go:generate mockery --testonly --with-expecter --name=Repository --structname=OrganizationRepository --filename=organization_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // Repository represents the interface to implement to handle the persistence of an Organization. 10 | type Repository interface { 11 | Get(ctx context.Context, organizationID string) (*Organization, error) 12 | Update(ctx context.Context, organizationID string, request UpdateRequest) (*Organization, error) 13 | } 14 | -------------------------------------------------------------------------------- /internal/domain/execution_command/test_helper/test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import "github.com/qovery/terraform-provider-qovery/internal/domain/execution_command" 4 | 5 | var ( 6 | DefaultArguments = []string{"./app", "run"} 7 | DefaultEntrypoint = "/" 8 | 9 | /// Exposed to tests needing to get such object without having to know internal sauce magic 10 | DefaultValidNewExecutionCommandParams = execution_command.NewExecutionCommandParams{ 11 | Arguments: DefaultArguments, 12 | Entrypoint: &DefaultEntrypoint, 13 | } 14 | DefaultValidExecutionCommand = execution_command.ExecutionCommand{ 15 | Arguments: DefaultArguments, 16 | Entrypoint: &DefaultEntrypoint, 17 | } 18 | ) 19 | -------------------------------------------------------------------------------- /docs/data-sources/annotations_group.md: -------------------------------------------------------------------------------- 1 | # qovery_annotations_group (Data Source) 2 | 3 | Provides a Qovery annotations group resource 4 | ## Example Usage 5 | ```terraform 6 | data "qovery_annotations_group" "my_annotations_group" { 7 | id = "" 8 | organization_id = "" 9 | } 10 | ``` 11 | 12 | 13 | ## Schema 14 | 15 | ### Required 16 | 17 | - `id` (String) Id of the annotations group 18 | - `organization_id` (String) Id of the organization. 19 | 20 | ### Optional 21 | 22 | - `annotations` (Map of String) annotations 23 | - `name` (String) name of the annotations group 24 | - `scopes` (Set of String) scopes of the annotations group 25 | 26 | -------------------------------------------------------------------------------- /internal/domain/labels_group/labels_group_service.go: -------------------------------------------------------------------------------- 1 | package labels_group 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Service interface { 8 | Create(ctx context.Context, organizationId string, request UpsertServiceRequest) (*LabelsGroup, error) 9 | Get(ctx context.Context, organizationId string, labelsGroupID string) (*LabelsGroup, error) 10 | Update(ctx context.Context, organizationId string, labelsGroupID string, request UpsertServiceRequest) (*LabelsGroup, error) 11 | Delete(ctx context.Context, organizationId string, labelsGroupID string) error 12 | } 13 | 14 | type UpsertServiceRequest struct { 15 | LabelsGroupUpsertRequest UpsertRequest 16 | } 17 | 18 | func (r UpsertServiceRequest) Validate() error { 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /scripts/fetch_instance_types.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o pipefail 3 | curl -f -s -H "Accept: application/json" -H "Authorization: Token $QOVERY_API_TOKEN" https://api.qovery.com/aws/instanceType | jq "[.results[] | .type] | sort" | jq -rc tostring | tr -d '\n' > qovery/data/cluster_instance_types/aws.json 4 | curl -f -s -H "Accept: application/json" -H "Authorization: Token $QOVERY_API_TOKEN" https://api.qovery.com/digitalOcean/instanceType | jq "[.results[] | .type] | sort" | jq -rc tostring | tr -d '\n' > qovery/data/cluster_instance_types/digital_ocean.json 5 | curl -f -s -H "Accept: application/json" -H "Authorization: Token $QOVERY_API_TOKEN" https://api.qovery.com/scaleway/instanceType | jq "[.results[] | .type] | sort" | jq -rc tostring | tr -d '\n' > qovery/data/cluster_instance_types/scaleway.json 6 | -------------------------------------------------------------------------------- /internal/domain/annotations_group/annotations_group_service.go: -------------------------------------------------------------------------------- 1 | package annotations_group 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Service interface { 8 | Create(ctx context.Context, organizationId string, request UpsertServiceRequest) (*AnnotationsGroup, error) 9 | Get(ctx context.Context, organizationId string, annotationsGroupID string) (*AnnotationsGroup, error) 10 | Update(ctx context.Context, organizationId string, annotationsGroupID string, request UpsertServiceRequest) (*AnnotationsGroup, error) 11 | Delete(ctx context.Context, organizationId string, annotationsGroupID string) error 12 | } 13 | 14 | type UpsertServiceRequest struct { 15 | AnnotationsGroupUpsertRequest UpsertRequest 16 | } 17 | 18 | func (r UpsertServiceRequest) Validate() error { 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /docs/data-sources/organization.md: -------------------------------------------------------------------------------- 1 | # qovery_organization (Data Source) 2 | 3 | Provides a Qovery organization resource. This can be used to create and manage Qovery organizations. 4 | ## Example Usage 5 | ```terraform 6 | data "qovery_organization" "my_organization" { 7 | id = "" 8 | } 9 | ``` 10 | 11 | 12 | ## Schema 13 | 14 | ### Required 15 | 16 | - `id` (String) Id of the organization. 17 | 18 | ### Read-Only 19 | 20 | - `description` (String) Description of the organization. 21 | - `name` (String) Name of the organization. 22 | - `plan` (String) Plan of the organization. 23 | - Can be: `BUSINESS`, `BUSINESS_2025`, `ENTERPRISE`, `ENTERPRISE_2025`, `ENTERPRISE_YEARLY`, `FREE`, `PROFESSIONAL`, `TEAM`, `TEAM_2025`, `TEAM_YEARLY`, `USER_2025`. 24 | 25 | -------------------------------------------------------------------------------- /internal/domain/gittoken/git_token_service.go: -------------------------------------------------------------------------------- 1 | package gittoken 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | ) 8 | 9 | type GitTokenParams struct { 10 | Name string 11 | Description *string 12 | Type string 13 | Token string 14 | BitbucketWorkspace *string 15 | } 16 | 17 | type Service interface { 18 | Create(ctx context.Context, organizationID string, params GitTokenParams) (*qovery.GitTokenResponse, error) 19 | Get(ctx context.Context, organizationID string, gitTokenID string) (*qovery.GitTokenResponse, error) 20 | Update(ctx context.Context, organizationID string, gitTokenID string, params GitTokenParams) (*qovery.GitTokenResponse, error) 21 | Delete(ctx context.Context, organizationID string, gitTokenID string) error 22 | } 23 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Qovery Provider 2 | 3 | The [Qovery](https://www.qovery.com/) provider is used to interact with the resources supported by Qovery. 4 | The provider needs to be configured with the proper credentials before it can be used. 5 | It requires terraform 1.0.3 or later. 6 | 7 | 8 | ## Example Usage 9 | 10 | ```terraform 11 | # Terraform 1.0.3+ uses the Terraform Registry: 12 | 13 | terraform { 14 | required_providers { 15 | qovery = { 16 | source = "qovery/qovery" 17 | } 18 | } 19 | } 20 | 21 | # Configure the Qovery provider 22 | provider "qovery" { 23 | token = "" 24 | } 25 | ``` 26 | 27 | 28 | ## Schema 29 | 30 | ### Optional 31 | 32 | - `token` (String) The Qovery API Token to use. This can also be specified with the `QOVERY_API_TOKEN` shell environment variable. -------------------------------------------------------------------------------- /docs/data-sources/labels_group.md: -------------------------------------------------------------------------------- 1 | # qovery_labels_group (Data Source) 2 | 3 | Provides a Qovery labels group resource 4 | ## Example Usage 5 | ```terraform 6 | data "qovery_labels_group" "my_labels_group" { 7 | id = "" 8 | organization_id = "" 9 | } 10 | ``` 11 | 12 | 13 | ## Schema 14 | 15 | ### Required 16 | 17 | - `id` (String) Id of the labels group 18 | - `organization_id` (String) Id of the organization. 19 | 20 | ### Optional 21 | 22 | - `labels` (Attributes Set) labels (see [below for nested schema](#nestedatt--labels)) 23 | - `name` (String) name of the labels group 24 | 25 | 26 | ### Nested Schema for `labels` 27 | 28 | Required: 29 | 30 | - `key` (String) 31 | - `propagate_to_cloud_provider` (Boolean) 32 | - `value` (String) 33 | 34 | -------------------------------------------------------------------------------- /internal/domain/deployment/deployment_repository.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | //go:generate mockery --testonly --with-expecter --name=Repository --structname=DeploymentRepository --filename=deployment_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/qovery/terraform-provider-qovery/internal/domain/status" 9 | ) 10 | 11 | // Repository represents the interface to implement to handle the deployments of qovery services. 12 | type Repository interface { 13 | GetStatus(ctx context.Context, resourceID string) (*status.Status, error) 14 | Deploy(ctx context.Context, resourceID string, version string) (*status.Status, error) 15 | Redeploy(ctx context.Context, resourceID string) (*status.Status, error) 16 | Stop(ctx context.Context, resourceID string) (*status.Status, error) 17 | } 18 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TF_ACC=1 2 | QOVERY_API_TOKEN= 3 | TEST_ORGANIZATION_ID= 4 | TEST_AWS_CREDENTIALS_ID= 5 | TEST_AWS_CREDENTIALS_ACCESS_KEY_ID= 6 | TEST_AWS_CREDENTIALS_SECRET_ACCESS_KEY= 7 | TEST_SCALEWAY_CREDENTIALS_ID= 8 | TEST_SCALEWAY_CREDENTIALS_PROJECT_ID= 9 | TEST_SCALEWAY_CREDENTIALS_ACCESS_KEY= 10 | TEST_SCALEWAY_CREDENTIALS_SECRET_KEY= 11 | TEST_CLUSTER_ID= 12 | TEST_PROJECT_ID= 13 | TEST_ENVIRONMENT_ID= 14 | TEST_APPLICATION_ID= 15 | TEST_DATABASE_ID= 16 | TEST_CONTAINER_REGISTRY_ID= -------------------------------------------------------------------------------- /internal/domain/helmRepository/helm_repository_repository.go: -------------------------------------------------------------------------------- 1 | package helmRepository 2 | 3 | //go:generate mockery --testonly --with-expecter --name=Repository --structname=RegistryRepository --filename=registry_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // Repository represents the interface to implement to handle the persistence of a Helm Repository. 10 | type Repository interface { 11 | Create(ctx context.Context, organizationID string, request UpsertRequest) (*HelmRepository, error) 12 | Get(ctx context.Context, organizationID string, registryID string) (*HelmRepository, error) 13 | Update(ctx context.Context, organizationID string, registryID string, request UpsertRequest) (*HelmRepository, error) 14 | Delete(ctx context.Context, organizationID string, registryID string) error 15 | } 16 | -------------------------------------------------------------------------------- /qovery/resource_docker_model.go: -------------------------------------------------------------------------------- 1 | package qovery 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/types" 5 | "github.com/qovery/terraform-provider-qovery/internal/domain/docker" 6 | ) 7 | 8 | type Docker struct { 9 | GitRepository GitRepository `tfsdk:"git_repository"` 10 | DockerFilePath types.String `tfsdk:"dockerfile_path"` 11 | DockerfileRaw types.String `tfsdk:"dockerfile_raw"` 12 | DockerTargetBuildStage types.String `tfsdk:"docker_target_build_stage"` 13 | } 14 | 15 | func (d Docker) toUpsertRequest() *docker.Docker { 16 | return &docker.Docker{ 17 | GitRepository: d.GitRepository.toUpsertRequest(), 18 | DockerFilePath: ToStringPointer(d.DockerFilePath), 19 | DockerFileRaw: ToStringPointer(d.DockerfileRaw), 20 | DockerTargetBuildStage: ToStringPointer(d.DockerTargetBuildStage), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /internal/domain/annotations_group/annotations_group_repository.go: -------------------------------------------------------------------------------- 1 | package annotations_group 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Repository interface { 8 | Create(ctx context.Context, organizationId string, request UpsertRequest) (*AnnotationsGroup, error) 9 | Get(ctx context.Context, organizationId string, annotationsGroupId string) (*AnnotationsGroup, error) 10 | Update(ctx context.Context, organizationId string, annotationsGroupId string, request UpsertRequest) (*AnnotationsGroup, error) 11 | Delete(ctx context.Context, organizationId string, annotationsGroupId string) error 12 | } 13 | 14 | type UpsertRequest struct { 15 | Name string `validate:"required"` 16 | Annotations []AnnotationUpsertRequest 17 | Scopes []string 18 | } 19 | 20 | type AnnotationUpsertRequest struct { 21 | Key string `validate:"required"` 22 | Value string `validate:"required"` 23 | } 24 | -------------------------------------------------------------------------------- /internal/domain/labels_group/labels_group_repository.go: -------------------------------------------------------------------------------- 1 | package labels_group 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type Repository interface { 8 | Create(ctx context.Context, organizationId string, request UpsertRequest) (*LabelsGroup, error) 9 | Get(ctx context.Context, organizationId string, labelsGroupId string) (*LabelsGroup, error) 10 | Update(ctx context.Context, organizationId string, labelsGroupId string, request UpsertRequest) (*LabelsGroup, error) 11 | Delete(ctx context.Context, organizationId string, labelsGroupId string) error 12 | } 13 | 14 | type UpsertRequest struct { 15 | Name string `validate:"required"` 16 | Labels []LabelUpsertRequest 17 | } 18 | 19 | type LabelUpsertRequest struct { 20 | Key string `validate:"required"` 21 | Value string `validate:"required"` 22 | PropagateToCloudProvider bool `validate:"required"` 23 | } 24 | -------------------------------------------------------------------------------- /internal/domain/credentials/credentials_aws_repository.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | //go:generate mockery --testonly --with-expecter --name=AwsRepository --structname=CredentialsAwsRepository --filename=credentials_aws_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // AwsRepository represents the interface to implement to handle the persistence of AWS Credentials. 10 | type AwsRepository interface { 11 | Create(ctx context.Context, organizationID string, request UpsertAwsRequest) (*Credentials, error) 12 | Get(ctx context.Context, organizationID string, credentialsID string) (*Credentials, error) 13 | Update(ctx context.Context, organizationID string, credentialsID string, request UpsertAwsRequest) (*Credentials, error) 14 | Delete(ctx context.Context, organizationID string, credentialsID string) error 15 | } 16 | -------------------------------------------------------------------------------- /internal/domain/status/status_state_test.go: -------------------------------------------------------------------------------- 1 | package status_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/status" 10 | ) 11 | 12 | // TestNewStateFromString validate that the kinds qovery.StateEnumEnum defined in Qovery's API Client are valid. 13 | // This is useful to make sure the status.State stays up to date. 14 | func TestNewStateFromString(t *testing.T) { 15 | t.Parallel() 16 | 17 | assert.Len(t, status.AllowedStateValues, len(qovery.AllowedStateEnumEnumValues)) 18 | for _, state := range qovery.AllowedStateEnumEnumValues { 19 | stateStr := string(state) 20 | t.Run(stateStr, func(t *testing.T) { 21 | s, err := status.NewStateFromString(stateStr) 22 | assert.NoError(t, err) 23 | assert.Equal(t, s.String(), stateStr) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/domain/newdeployment/newdeployment_repository.go: -------------------------------------------------------------------------------- 1 | package newdeployment 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | type EnvironmentRepository interface { 10 | Deploy(ctx context.Context, newDeployment Deployment) (*Deployment, error) 11 | ReDeploy(ctx context.Context, newDeployment Deployment) (*Deployment, error) 12 | Stop(ctx context.Context, newDeployment Deployment) (*Deployment, error) 13 | Restart(ctx context.Context, newDeployment Deployment) (*Deployment, error) 14 | Delete(ctx context.Context, newDeployment Deployment) (*Deployment, error) 15 | } 16 | 17 | type DeploymentStatusRepository interface { 18 | WaitForTerminatedState(ctx context.Context, environmentId uuid.UUID) error 19 | WaitForExpectedDesiredState(ctx context.Context, newDeployment Deployment) error 20 | CheckEnvironmentExists(ctx context.Context, environmentId uuid.UUID) (error, int) 21 | } 22 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | ) 8 | 9 | type Client struct { 10 | api *qovery.APIClient 11 | } 12 | 13 | func New(token string, version string, host string) *Client { 14 | return &Client{ 15 | NewQoveryAPIClient(token, version, host), 16 | } 17 | } 18 | 19 | // NewQoveryAPIClient used for tests only 20 | func NewQoveryAPIClient(token string, version string, host string) *qovery.APIClient { 21 | cfg := qovery.NewConfiguration() 22 | cfg.AddDefaultHeader("Authorization", fmt.Sprintf("Token %s", token)) 23 | cfg.AddDefaultHeader("content-type", "application/json") 24 | 25 | cfg.UserAgent = fmt.Sprintf("Terraform provider %s", version) 26 | cfg.Servers = qovery.ServerConfigurations{ 27 | { 28 | URL: host, 29 | Description: "No description provided", 30 | }, 31 | } 32 | 33 | return qovery.NewAPIClient(cfg) 34 | } 35 | -------------------------------------------------------------------------------- /qovery/descriptions/map_string_array_enum_description.go: -------------------------------------------------------------------------------- 1 | package descriptions 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | func NewMapStringArrayEnumDescription(description string, enum map[string][]string, defaultValue *string) string { 10 | desc := fmt.Sprintf("%s", description) 11 | 12 | keys := sortedMapKeys(enum) 13 | for _, key := range keys { 14 | values := enum[key] 15 | sort.Strings(values) 16 | desc += fmt.Sprintf( 17 | "\n\t- %s: `%s`.", 18 | key, 19 | strings.Join(values, "`, `"), 20 | ) 21 | } 22 | 23 | if defaultValue != nil { 24 | desc += fmt.Sprintf( 25 | "\n\t- Default: `%s`.", 26 | *defaultValue, 27 | ) 28 | } 29 | 30 | return desc 31 | } 32 | 33 | func sortedMapKeys[T any](m map[string]T) []string { 34 | keys := make([]string, 0, len(m)) 35 | for k := range m { 36 | keys = append(keys, k) 37 | } 38 | sort.Strings(keys) 39 | return keys 40 | } 41 | -------------------------------------------------------------------------------- /internal/domain/registry/registry_repository.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | //go:generate mockery --testonly --with-expecter --name=Repository --structname=RegistryRepository --filename=registry_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // Repository represents the interface to implement to handle the persistence of a Registry. 10 | // registryID can be either a registryID, environmentID, application or containerID 11 | type Repository interface { 12 | Create(ctx context.Context, organizationID string, request UpsertRequest) (*Registry, error) 13 | Get(ctx context.Context, organizationID string, registryID string) (*Registry, error) 14 | Update(ctx context.Context, organizationID string, registryID string, request UpsertRequest) (*Registry, error) 15 | Delete(ctx context.Context, organizationID string, registryID string) error 16 | } 17 | -------------------------------------------------------------------------------- /internal/domain/gittoken/git_token_type.go: -------------------------------------------------------------------------------- 1 | package gittoken 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | type GitTokenType string 10 | 11 | const ( 12 | GITHUB GitTokenType = "GITHUB" 13 | GITLAB GitTokenType = "GITLAB" 14 | BITBUCKET GitTokenType = "BITBUCKET" 15 | ) 16 | 17 | var AllowedGitTokenTypeValues = []GitTokenType{GITHUB, GITLAB, BITBUCKET} 18 | 19 | func (v GitTokenType) String() string { 20 | return string(v) 21 | } 22 | 23 | func (v GitTokenType) Validate() error { 24 | if slices.Contains(AllowedGitTokenTypeValues, v) { 25 | return nil 26 | } 27 | 28 | return fmt.Errorf("invalid value '%v' for Kind: valid values are %v", v, AllowedGitTokenTypeValues) 29 | } 30 | 31 | func NewGitTokenTypeFromString(v string) (*GitTokenType, error) { 32 | ev := GitTokenType(v) 33 | 34 | if err := ev.Validate(); err != nil { 35 | return nil, err 36 | } 37 | 38 | return &ev, nil 39 | } 40 | -------------------------------------------------------------------------------- /qovery/git_helpers.go: -------------------------------------------------------------------------------- 1 | package qovery 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/qovery/qovery-client-go" 8 | ) 9 | 10 | // detectGitProviderFromURL detects the git provider from a git repository URL. 11 | // It returns the corresponding GitProviderEnum or an error if the provider cannot be determined. 12 | func detectGitProviderFromURL(url string) (qovery.GitProviderEnum, error) { 13 | urlLower := strings.ToLower(url) 14 | 15 | switch { 16 | case strings.Contains(urlLower, "github.com"): 17 | return qovery.GITPROVIDERENUM_GITHUB, nil 18 | case strings.Contains(urlLower, "gitlab.com"): 19 | return qovery.GITPROVIDERENUM_GITLAB, nil 20 | case strings.Contains(urlLower, "bitbucket.org"): 21 | return qovery.GITPROVIDERENUM_BITBUCKET, nil 22 | default: 23 | return "", fmt.Errorf("unable to detect git provider from URL: %s. Supported providers are: github.com, gitlab.com, bitbucket.org", url) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /internal/domain/credentials/credentials_scaleway_repository.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | //go:generate mockery --testonly --with-expecter --name=ScalewayRepository --structname=CredentialsScalewayRepository --filename=credentials_scaleway_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | ) 8 | 9 | // ScalewayRepository represents the interface to implement to handle the persistence of AWS Credentials. 10 | type ScalewayRepository interface { 11 | Create(ctx context.Context, organizationID string, request UpsertScalewayRequest) (*Credentials, error) 12 | Get(ctx context.Context, organizationID string, credentialsID string) (*Credentials, error) 13 | Update(ctx context.Context, organizationID string, credentialsID string, request UpsertScalewayRequest) (*Credentials, error) 14 | Delete(ctx context.Context, organizationID string, credentialsID string) error 15 | } 16 | -------------------------------------------------------------------------------- /internal/domain/execution_command/execution_command.go: -------------------------------------------------------------------------------- 1 | package execution_command 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | var ( 8 | // ErrInvalidArgumentsParam is returned if the URL param is invalid. 9 | ErrInvalidArgumentsParam = errors.New("invalid arguments param") 10 | ) 11 | 12 | type ExecutionCommand struct { 13 | Entrypoint *string 14 | Arguments []string 15 | } 16 | 17 | func (e ExecutionCommand) Validate() error { 18 | return nil 19 | } 20 | 21 | type NewExecutionCommandParams struct { 22 | Entrypoint *string 23 | Arguments []string 24 | } 25 | 26 | func NewExecutionCommand(params NewExecutionCommandParams) (*ExecutionCommand, error) { 27 | newExecutionCommand := &ExecutionCommand{ 28 | Entrypoint: params.Entrypoint, 29 | Arguments: params.Arguments, 30 | } 31 | 32 | if err := newExecutionCommand.Validate(); err != nil { 33 | return nil, err 34 | } 35 | 36 | return newExecutionCommand, nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/domain/port/port_protocol_test.go: -------------------------------------------------------------------------------- 1 | package port_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/port" 10 | ) 11 | 12 | // TestNewProtocolFromString validate that the kinds qovery.PortProtocolEnum defined in Qovery's API Client are valid. 13 | // This is useful to make sure the port.Protocol stays up to date. 14 | func TestNewProtocolFromString(t *testing.T) { 15 | t.Parallel() 16 | 17 | assert.Len(t, port.AllowedProtocolValues, len(qovery.AllowedPortProtocolEnumEnumValues)) 18 | for _, portType := range qovery.AllowedPortProtocolEnumEnumValues { 19 | portTypeStr := string(portType) 20 | t.Run(portTypeStr, func(t *testing.T) { 21 | st, err := port.NewProtocolFromString(portTypeStr) 22 | assert.NoError(t, err) 23 | assert.Equal(t, st.String(), portTypeStr) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/domain/storage/storage_type_test.go: -------------------------------------------------------------------------------- 1 | package storage_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/storage" 10 | ) 11 | 12 | // TestNewTypeFromString validate that the kinds qovery.StorageTypeEnum defined in Qovery's API Client are valid. 13 | // This is useful to make sure the storage.Type stays up to date. 14 | func TestNewTypeFromString(t *testing.T) { 15 | t.Parallel() 16 | 17 | assert.Len(t, storage.AllowedTypeValues, len(qovery.AllowedStorageTypeEnumEnumValues)) 18 | for _, storageType := range qovery.AllowedStorageTypeEnumEnumValues { 19 | storageTypeStr := string(storageType) 20 | t.Run(storageTypeStr, func(t *testing.T) { 21 | st, err := storage.NewTypeFromString(storageTypeStr) 22 | assert.NoError(t, err) 23 | assert.Equal(t, st.String(), storageTypeStr) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/domain/registry/registry_service.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | ErrFailedToCreateRegistry = errors.New("failed to create registry") 11 | ErrFailedToGetRegistry = errors.New("failed to get registry") 12 | ErrFailedToUpdateRegistry = errors.New("failed to update registry") 13 | ErrFailedToDeleteRegistry = errors.New("failed to delete registry") 14 | ) 15 | 16 | // Service represents the interface to implement to handle the domain logic of an Registry. 17 | type Service interface { 18 | Create(ctx context.Context, organizationID string, request UpsertRequest) (*Registry, error) 19 | Get(ctx context.Context, organizationID string, registryID string) (*Registry, error) 20 | Update(ctx context.Context, organizationID string, registryID string, request UpsertRequest) (*Registry, error) 21 | Delete(ctx context.Context, organizationID string, registryID string) error 22 | } 23 | -------------------------------------------------------------------------------- /internal/domain/organization/organization_plan_test.go: -------------------------------------------------------------------------------- 1 | package organization_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/organization" 10 | ) 11 | 12 | // TestNewPlanFromString validate that the plans qovery.PlanEnum defined in Qovery's API Client are valid. 13 | // This is useful to make sure the organization.Plan stays up to date. 14 | func TestNewPlanFromString(t *testing.T) { 15 | t.Parallel() 16 | 17 | assert.Len(t, organization.AllowedPlanValues, len(qovery.AllowedPlanEnumEnumValues)) 18 | for _, qoveryPlan := range qovery.AllowedPlanEnumEnumValues { 19 | qoveryPlanStr := string(qoveryPlan) 20 | t.Run(qoveryPlanStr, func(t *testing.T) { 21 | plan, err := organization.NewPlanFromString(qoveryPlanStr) 22 | assert.NoError(t, err) 23 | assert.Equal(t, plan.String(), qoveryPlanStr) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/domain/newdeployment/newdeployment_service.go: -------------------------------------------------------------------------------- 1 | package newdeployment 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | ErrFailedToCreateDeployment = errors.New("failed to create deployment") 11 | ErrFailedToGetDeployment = errors.New("failed to get deployment") 12 | ErrFailedToDeleteDeployment = errors.New("failed to delete deployment") 13 | ErrDesiredStateForbiddenAtCreation = errors.New("Cannot create a deployment having state 'DELETED' or 'RESTARTED'") 14 | ErrFailedToCheckDeploymentStatus = errors.New("failed to retrieve deployment status") 15 | ) 16 | 17 | type Service interface { 18 | Create(ctx context.Context, params NewDeploymentParams) (*Deployment, error) 19 | Get(ctx context.Context, params NewDeploymentParams) (*Deployment, error) 20 | Update(ctx context.Context, params NewDeploymentParams) (*Deployment, error) 21 | Delete(ctx context.Context, params NewDeploymentParams) error 22 | } 23 | -------------------------------------------------------------------------------- /internal/infrastructure/repositories/qoveryapi/git_helpers.go: -------------------------------------------------------------------------------- 1 | package qoveryapi 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/qovery/qovery-client-go" 8 | ) 9 | 10 | // detectGitProviderFromURL detects the git provider from a git repository URL. 11 | // It returns the corresponding GitProviderEnum or an error if the provider cannot be determined. 12 | func detectGitProviderFromURL(url string) (qovery.GitProviderEnum, error) { 13 | urlLower := strings.ToLower(url) 14 | 15 | switch { 16 | case strings.Contains(urlLower, "github.com"): 17 | return qovery.GITPROVIDERENUM_GITHUB, nil 18 | case strings.Contains(urlLower, "gitlab.com"): 19 | return qovery.GITPROVIDERENUM_GITLAB, nil 20 | case strings.Contains(urlLower, "bitbucket.org"): 21 | return qovery.GITPROVIDERENUM_BITBUCKET, nil 22 | default: 23 | return "", fmt.Errorf("unable to detect git provider from URL: %s. Supported providers are: github.com, gitlab.com, bitbucket.org", url) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/custom_domains.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/qovery/qovery-client-go" 5 | ) 6 | 7 | type CustomDomainsDiff struct { 8 | Create []CustomDomainCreateRequest 9 | Update []CustomDomainUpdateRequest 10 | Delete []CustomDomainDeleteRequest 11 | } 12 | 13 | func (d CustomDomainsDiff) IsEmpty() bool { 14 | return len(d.Create) == 0 && 15 | len(d.Update) == 0 && 16 | len(d.Delete) == 0 17 | } 18 | 19 | type CustomDomainCreateRequest struct { 20 | qovery.CustomDomainRequest 21 | } 22 | 23 | type CustomDomainUpdateRequest struct { 24 | qovery.CustomDomainRequest 25 | Id string 26 | } 27 | 28 | type CustomDomainDeleteRequest struct { 29 | Id string 30 | } 31 | 32 | func customDomainResponseListToArray(list *qovery.CustomDomainResponseList) []*qovery.CustomDomain { 33 | vars := make([]*qovery.CustomDomain, 0, len(list.GetResults())) 34 | for _, v := range list.GetResults() { 35 | cpy := v 36 | vars = append(vars, &cpy) 37 | } 38 | return vars 39 | } 40 | -------------------------------------------------------------------------------- /internal/domain/environment/environment_mode_test.go: -------------------------------------------------------------------------------- 1 | package environment_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/environment" 10 | ) 11 | 12 | // TestNewModeFromString validate that the modes qovery.EnvironmentModeEnum defined in Qovery's API Client are valid. 13 | // This is useful to make sure the environment.Mode stays up to date. 14 | func TestNewModeFromString(t *testing.T) { 15 | t.Parallel() 16 | 17 | assert.Len(t, environment.AllowedModeValues, len(qovery.AllowedEnvironmentModeEnumEnumValues)) 18 | for _, qoveryMode := range qovery.AllowedEnvironmentModeEnumEnumValues { 19 | qoveryModeStr := string(qoveryMode) 20 | t.Run(qoveryModeStr, func(t *testing.T) { 21 | mode, err := environment.NewModeFromString(qoveryModeStr) 22 | assert.NoError(t, err) 23 | assert.Equal(t, mode.String(), qoveryModeStr) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/domain/registry/registry_kind_test.go: -------------------------------------------------------------------------------- 1 | package registry_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/registry" 10 | ) 11 | 12 | // TestNewKindFromString validate that the kinds qovery.ContainerRegistryKindEnum defined in Qovery's API Client are valid. 13 | // This is useful to make sure the registry.Kind stays up to date. 14 | func TestNewKindFromString(t *testing.T) { 15 | t.Parallel() 16 | 17 | assert.Len(t, registry.AllowedKindValues, len(qovery.AllowedContainerRegistryKindEnumEnumValues)) 18 | for _, registryKind := range qovery.AllowedContainerRegistryKindEnumEnumValues { 19 | registryKindStr := string(registryKind) 20 | t.Run(registryKindStr, func(t *testing.T) { 21 | kind, err := registry.NewKindFromString(registryKindStr) 22 | assert.NoError(t, err) 23 | assert.Equal(t, kind.String(), registryKindStr) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/domain/variable/variable_scope_test.go: -------------------------------------------------------------------------------- 1 | package variable_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/variable" 10 | ) 11 | 12 | // TestNewScopeFromString validate that the scopes qovery.APIVariableScopeEnum defined in Qovery's API Client are valid. 13 | // This is useful to make sure the variable.Scope stays up to date. 14 | func TestNewScopeFromString(t *testing.T) { 15 | t.Parallel() 16 | 17 | assert.Len(t, variable.AllowedScopeValues, len(qovery.AllowedAPIVariableScopeEnumEnumValues)) 18 | for _, variableScope := range qovery.AllowedAPIVariableScopeEnumEnumValues { 19 | variableScopeStr := string(variableScope) 20 | t.Run(variableScopeStr, func(t *testing.T) { 21 | scope, err := variable.NewScopeFromString(variableScopeStr) 22 | assert.NoError(t, err) 23 | assert.Equal(t, scope.String(), variableScopeStr) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/data-sources/container_registry.md: -------------------------------------------------------------------------------- 1 | # qovery_container_registry (Data Source) 2 | 3 | Provides a Qovery container registry resource. This can be used to create and manage Qovery container registry. 4 | ## Example Usage 5 | ```terraform 6 | data "qovery_container_registry" "my_container_registry" { 7 | id = "" 8 | } 9 | ``` 10 | 11 | 12 | ## Schema 13 | 14 | ### Required 15 | 16 | - `id` (String) Id of the container registry. 17 | - `organization_id` (String) Id of the organization. 18 | 19 | ### Optional 20 | 21 | - `description` (String) Description of the container registry. 22 | 23 | ### Read-Only 24 | 25 | - `kind` (String) Kind of the container registry. 26 | - Can be: `AZURE_CR`, `DOCKER_HUB`, `DOCR`, `ECR`, `GCP_ARTIFACT_REGISTRY`, `GENERIC_CR`, `GITHUB_CR`, `GITHUB_ENTERPRISE_CR`, `GITLAB_CR`, `PUBLIC_ECR`, `SCALEWAY_CR`. 27 | - `name` (String) Name of the container registry. 28 | - `url` (String) URL of the container registry. 29 | 30 | -------------------------------------------------------------------------------- /internal/domain/job/test_helper/job_source_test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | image_test_helper "github.com/qovery/terraform-provider-qovery/internal/domain/image/test_helper" 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/job" 7 | ) 8 | 9 | var ( 10 | DefaultNewJobSourceParams = job.NewJobSourceParams{ 11 | Image: &image_test_helper.DefaultValidNewImageParams, 12 | Docker: nil, 13 | } 14 | 15 | DefaultJobSource = job.Source{ 16 | Image: &image_test_helper.DefaultValidImage, 17 | Docker: nil, 18 | } 19 | 20 | DefaultNewInvalidJobSourceParams = job.NewJobSourceParams{ 21 | Image: &image_test_helper.DefaultInvalidNewImageParams, 22 | Docker: nil, 23 | } 24 | 25 | DefaultInvalidJobSource = job.Source{ 26 | Image: &image_test_helper.DefaultInvalidImage, 27 | Docker: nil, 28 | } 29 | 30 | DefaultInvalidNewJobSourceParamsError = errors.Wrap(image_test_helper.DefaultInvalidNewImageParamsError, job.ErrInvalidJobSourceImageParam.Error()) 31 | ) 32 | -------------------------------------------------------------------------------- /client/secrets.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/qovery/qovery-client-go" 5 | ) 6 | 7 | type SecretsDiff struct { 8 | Create []SecretCreateRequest 9 | Update []SecretUpdateRequest 10 | Delete []SecretDeleteRequest 11 | } 12 | 13 | func (d SecretsDiff) IsEmpty() bool { 14 | return len(d.Create) == 0 && 15 | len(d.Update) == 0 && 16 | len(d.Delete) == 0 17 | } 18 | 19 | type SecretCreateRequest struct { 20 | qovery.SecretRequest 21 | } 22 | 23 | type SecretUpdateRequest struct { 24 | qovery.SecretEditRequest 25 | Id string 26 | } 27 | 28 | type SecretDeleteRequest struct { 29 | Id string 30 | } 31 | 32 | func secretResponseListToArray(list *qovery.SecretResponseList, scope qovery.APIVariableScopeEnum) []*qovery.Secret { 33 | vars := make([]*qovery.Secret, 0, len(list.GetResults())) 34 | for _, v := range list.GetResults() { 35 | if v.Scope != scope && v.Scope != qovery.APIVARIABLESCOPEENUM_BUILT_IN { 36 | continue 37 | } 38 | cpy := v 39 | vars = append(vars, &cpy) 40 | } 41 | return vars 42 | } 43 | -------------------------------------------------------------------------------- /internal/domain/status/service_deployment_status_test.go: -------------------------------------------------------------------------------- 1 | package status_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/status" 10 | ) 11 | 12 | // TestNewServiceDeploymentStatusFromString validate that the kinds qovery.ServiceDeploymentStatusEnumEnum defined in Qovery's API Client are valid. 13 | // This is useful to make sure the status.ServiceDeploymentStatus stays up to date. 14 | func TestNewServiceDeploymentStatusFromString(t *testing.T) { 15 | t.Parallel() 16 | 17 | assert.Len(t, status.AllowedServiceDeploymentStatusValues, len(qovery.AllowedServiceDeploymentStatusEnumEnumValues)) 18 | for _, sds := range qovery.AllowedServiceDeploymentStatusEnumEnumValues { 19 | sdsStr := string(sds) 20 | t.Run(sdsStr, func(t *testing.T) { 21 | s, err := status.NewServiceDeploymentStatusFromString(sdsStr) 22 | assert.NoError(t, err) 23 | assert.Equal(t, s.String(), sdsStr) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /internal/domain/credentials/credentials_aws_service.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | ErrFailedToCreateAwsCredentials = errors.New("failed to create aws credentials") 11 | ErrFailedToGetAwsCredentials = errors.New("failed to get aws credentials") 12 | ErrFailedToUpdateAwsCredentials = errors.New("failed to update aws credentials") 13 | ErrFailedToDeleteAwsCredentials = errors.New("failed to delete aws credentials") 14 | ) 15 | 16 | // AwsService represents the interface to implement to handle the domain logic of AWS Credentials. 17 | type AwsService interface { 18 | Create(ctx context.Context, organizationID string, request UpsertAwsRequest) (*Credentials, error) 19 | Get(ctx context.Context, organizationID string, credentialsID string) (*Credentials, error) 20 | Update(ctx context.Context, organizationID string, credentialsID string, request UpsertAwsRequest) (*Credentials, error) 21 | Delete(ctx context.Context, organizationID string, credentialsID string) error 22 | } 23 | -------------------------------------------------------------------------------- /internal/domain/labels_group/labels_group.go: -------------------------------------------------------------------------------- 1 | package labels_group 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "github.com/pkg/errors" 6 | "github.com/qovery/qovery-client-go" 7 | ) 8 | 9 | var ( 10 | ErrInvalidLabelsGroupRequest = errors.New("invalid labels group request") 11 | ErrInvalidLabelsGroupOrganizationIdParam = errors.New("invalid organization id format") 12 | ErrFailedToCreateLabelsGroup = errors.New("failed to create labels group") 13 | ErrFailedToGetLabelsGroup = errors.New("failed to get labels group") 14 | ErrFailedToUpdateLabelsGroup = errors.New("failed to update labels group") 15 | ErrFailedToDeleteLabelsGroup = errors.New("failed to delete labels group") 16 | ErrInvalidLabelsGroupIdParam = errors.New("invalid labels group id format") 17 | ErrInvalidScope = errors.New("invalid scope") 18 | ) 19 | 20 | type LabelsGroup struct { 21 | Id uuid.UUID `validate:"required"` 22 | Name string 23 | Labels []qovery.Label 24 | } 25 | -------------------------------------------------------------------------------- /qovery/resource_organization_model.go: -------------------------------------------------------------------------------- 1 | package qovery 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/types" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/organization" 7 | ) 8 | 9 | type Organization struct { 10 | Id types.String `tfsdk:"id"` 11 | Name types.String `tfsdk:"name"` 12 | Plan types.String `tfsdk:"plan"` 13 | Description types.String `tfsdk:"description"` 14 | } 15 | 16 | func (org Organization) toOrganizationUpdateRequest() organization.UpdateRequest { 17 | return organization.UpdateRequest{ 18 | Name: ToString(org.Name), 19 | Description: ToStringPointer(org.Description), 20 | } 21 | } 22 | 23 | func convertDomainOrganizationToTerraform(organization *organization.Organization) Organization { 24 | return Organization{ 25 | Id: FromString(organization.ID.String()), 26 | Name: FromString(organization.Name), 27 | Plan: fromClientEnum(organization.Plan), 28 | Description: FromStringPointer(organization.Description), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/resources/qovery_database/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_database" "my_container_database" { 2 | # Required 3 | environment_id = qovery_environment.my_environment.id 4 | name = "MyContainerDatabase" 5 | type = "POSTGRESQL" 6 | version = "10" 7 | mode = "CONTAINER" 8 | 9 | # Optional 10 | accessibility = "PRIVATE" 11 | cpu = 250 12 | memory = 256 13 | storage = 10 14 | 15 | depends_on = [ 16 | qovery_environment.my_environment 17 | ] 18 | } 19 | 20 | resource "qovery_database" "my_managed_database" { 21 | # Required 22 | environment_id = qovery_environment.my_environment.id 23 | name = "MyManagedDatabase" 24 | type = "POSTGRESQL" 25 | version = "10" 26 | mode = "MANAGED" 27 | 28 | # Instance type to be set for managed databases 29 | instance_type = "db.t3.micro" 30 | 31 | # Optional 32 | accessibility = "PRIVATE" 33 | storage = 10 34 | 35 | depends_on = [ 36 | qovery_environment.my_environment 37 | ] 38 | } -------------------------------------------------------------------------------- /internal/domain/helm/helm_port_protocol.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | type Protocol string 10 | 11 | const ( 12 | ProtocolHttp Protocol = "HTTP" 13 | ProtocolGrpc Protocol = "GRPC" 14 | ) 15 | 16 | var AllowedProtocols = []Protocol{ 17 | ProtocolHttp, 18 | ProtocolGrpc, 19 | } 20 | 21 | func (v Protocol) String() string { 22 | return string(v) 23 | } 24 | 25 | // Validate returns an error to tell whether the Kind is valid or not. 26 | func (v Protocol) Validate() error { 27 | if slices.Contains(AllowedProtocols, v) { 28 | return nil 29 | } 30 | 31 | return fmt.Errorf("invalid value '%v' for Protocol: valid values are %v", v, AllowedProtocols) 32 | } 33 | 34 | // IsValid returns a bool to tell whether the Kind is valid or not. 35 | func (v Protocol) IsValid() bool { 36 | return v.Validate() == nil 37 | } 38 | 39 | func NewProtocolFromString(v string) (*Protocol, error) { 40 | ev := Protocol(v) 41 | 42 | if err := ev.Validate(); err != nil { 43 | return nil, err 44 | } 45 | 46 | return &ev, nil 47 | } 48 | -------------------------------------------------------------------------------- /docs/data-sources/helm_repository.md: -------------------------------------------------------------------------------- 1 | # qovery_helm_repository (Data Source) 2 | 3 | Provides a Qovery helm repository resource. This can be used to create and manage Qovery helm repository. 4 | ## Example Usage 5 | ```terraform 6 | data "qovery_helm_repository" "my_helm_repository" { 7 | id = "" 8 | organization_id = "" 9 | } 10 | ``` 11 | 12 | 13 | ## Schema 14 | 15 | ### Required 16 | 17 | - `id` (String) Id of the helm repository. 18 | - `organization_id` (String) Id of the organization. 19 | 20 | ### Optional 21 | 22 | - `description` (String) Description of the helm repository. 23 | - `kind` (String) Kind of the helm repository. 24 | - Can be: `HTTPS`, `OCI_DOCKER_HUB`, `OCI_DOCR`, `OCI_ECR`, `OCI_GENERIC_CR`, `OCI_GITHUB_CR`, `OCI_GITLAB_CR`, `OCI_PUBLIC_ECR`, `OCI_SCALEWAY_CR`. 25 | - `name` (String) Name of the helm repository. 26 | - `skip_tls_verification` (Boolean) Bypass tls certificate verification when connecting to repository 27 | - `url` (String) URL of the helm repository. 28 | 29 | -------------------------------------------------------------------------------- /internal/domain/helmRepository/helm_repository_service.go: -------------------------------------------------------------------------------- 1 | package helmRepository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | ErrFailedToCreateHelmRepository = errors.New("failed to create helm repository") 11 | ErrFailedToGetHelmRepository = errors.New("failed to get helm repository") 12 | ErrFailedToUpdateHelmRepository = errors.New("failed to update helm repository") 13 | ErrFailedToDeleteHelmRepository = errors.New("failed to delete helm repository") 14 | ErrInvalidOrganizationIdParam = errors.New("invalid organization Id") 15 | ErrInvalidRepositoryIdParam = errors.New("invalid repository Id") 16 | ) 17 | 18 | type Service interface { 19 | Create(ctx context.Context, organizationID string, request UpsertRequest) (*HelmRepository, error) 20 | Get(ctx context.Context, organizationID string, registryID string) (*HelmRepository, error) 21 | Update(ctx context.Context, organizationID string, registryID string, request UpsertRequest) (*HelmRepository, error) 22 | Delete(ctx context.Context, organizationID string, registryID string) error 23 | } 24 | -------------------------------------------------------------------------------- /qovery/resource_git_repository_model.go: -------------------------------------------------------------------------------- 1 | package qovery 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/types" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/git_repository" 7 | ) 8 | 9 | type GitRepository struct { 10 | Url types.String `tfsdk:"url"` 11 | Branch types.String `tfsdk:"branch"` 12 | RootPath types.String `tfsdk:"root_path"` 13 | GitTokenId types.String `tfsdk:"git_token_id"` 14 | } 15 | 16 | func (g GitRepository) toUpsertRequest() git_repository.GitRepository { 17 | var branch *string = nil 18 | if !g.Branch.IsNull() { 19 | v := ToString(g.Branch) 20 | branch = &v 21 | } 22 | 23 | var rootPath *string = nil 24 | if !g.RootPath.IsNull() { 25 | v := ToString(g.RootPath) 26 | rootPath = &v 27 | } 28 | 29 | var gitTokenId *string = nil 30 | if !g.GitTokenId.IsNull() { 31 | v := ToString(g.GitTokenId) 32 | gitTokenId = &v 33 | } 34 | 35 | return git_repository.GitRepository{ 36 | Url: ToString(g.Url), 37 | Branch: branch, 38 | RootPath: rootPath, 39 | GitTokenId: gitTokenId, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/domain/credentials/credentials_scaleway_service.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | ErrFailedToCreateScalewayCredentials = errors.New("failed to create scaleway credentials") 11 | ErrFailedToGetScalewayCredentials = errors.New("failed to get scaleway credentials") 12 | ErrFailedToUpdateScalewayCredentials = errors.New("failed to update scaleway credentials") 13 | ErrFailedToDeleteScalewayCredentials = errors.New("failed to delete scaleway credentials") 14 | ) 15 | 16 | // ScalewayService represents the interface to implement to handle the domain logic of AWS Credentials. 17 | type ScalewayService interface { 18 | Create(ctx context.Context, organizationID string, request UpsertScalewayRequest) (*Credentials, error) 19 | Get(ctx context.Context, organizationID string, credentialsID string) (*Credentials, error) 20 | Update(ctx context.Context, organizationID string, credentialsID string, request UpsertScalewayRequest) (*Credentials, error) 21 | Delete(ctx context.Context, organizationID string, credentialsID string) error 22 | } 23 | -------------------------------------------------------------------------------- /internal/domain/secret/test_helper/test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "github.com/pkg/errors" 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/secret" 7 | "github.com/qovery/terraform-provider-qovery/internal/domain/variable" 8 | ) 9 | 10 | var ( 11 | DefaultValidSecret = secret.Secret{ 12 | ID: uuid.New(), 13 | Scope: variable.ScopeApplication, 14 | Key: "SecretKey", 15 | } 16 | 17 | DefaultValidSecretParams = secret.NewSecretParams{ 18 | SecretID: uuid.New().String(), 19 | Scope: variable.ScopeApplication.String(), 20 | Key: "SecretKey", 21 | } 22 | 23 | DefaultInvalidSecret = secret.Secret{ 24 | ID: uuid.New(), 25 | Scope: variable.ScopeApplication, 26 | Key: "", 27 | } 28 | 29 | DefaultInvalidSecretParams = secret.NewSecretParams{ 30 | SecretID: uuid.New().String(), 31 | Scope: variable.ScopeApplication.String(), 32 | Key: "", 33 | } 34 | 35 | DefaultInvalidSecretParamsError = errors.New("invalid secret: Key: 'Secret.Key' Error:Field validation for 'Key' failed on the 'required' tag") 36 | ) 37 | -------------------------------------------------------------------------------- /internal/infrastructure/repositories/qoveryapi/status_qoveryapi_models.go: -------------------------------------------------------------------------------- 1 | package qoveryapi 2 | 3 | import ( 4 | "github.com/qovery/qovery-client-go" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/status" 7 | ) 8 | 9 | func newDomainStatusFromQovery(qoveryStatus *qovery.Status) (*status.Status, error) { 10 | if qoveryStatus == nil { 11 | return nil, status.ErrNilStatus 12 | } 13 | 14 | return status.NewStatus(status.NewStatusParams{ 15 | StatusID: qoveryStatus.Id, 16 | ServiceDeploymentStatus: string(qoveryStatus.ServiceDeploymentStatus), 17 | State: string(qoveryStatus.State), 18 | LastDeploymentDate: qoveryStatus.LastDeploymentDate, 19 | }) 20 | } 21 | func newDomainEnvironmentStatusFromQovery(qoveryStatus *qovery.EnvironmentStatus) (*status.Status, error) { 22 | if qoveryStatus == nil { 23 | return nil, status.ErrNilStatus 24 | } 25 | 26 | return status.NewStatus(status.NewStatusParams{ 27 | StatusID: qoveryStatus.Id, 28 | State: string(qoveryStatus.State), 29 | LastDeploymentDate: qoveryStatus.LastDeploymentDate.Get(), 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /templates/resources.md.tmpl: -------------------------------------------------------------------------------- 1 | # {{ .Name }} ({{ .Type }}) 2 | 3 | {{ printf "%s" .Description }} 4 | 5 | {{ if .HasExample }} 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | {{ tffile .ExampleFile }} 13 | 14 | {{ with $path := printf "./examples/resources/%s/examples.md" .Name -}} 15 | {{- with $content := codefile "" $path -}} 16 | {{- if lt 10 (len (plainmarkdown $content)) -}} 17 | You can find complete examples within these repositories: 18 | {{ plainmarkdown $content }} 19 | 20 | {{- end -}} 21 | {{- end -}} 22 | {{- end -}} 23 | 24 | {{ .SchemaMarkdown | trimspace }} 25 | {{- end }} 26 | {{ if .HasImport -}} 27 | ## Import 28 | {{ codefile "shell" .ImportFile }} 29 | {{- end -}} 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/resources/qovery_project/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_project" "my_project" { 2 | # Required 3 | organization_id = qovery_organization.my_organization.id 4 | name = "MyProject" 5 | 6 | # Optional 7 | description = "My project description" 8 | environment_variables = [ 9 | { 10 | key = "ENV_VAR_KEY" 11 | value = "ENV_VAR_VALUE" 12 | } 13 | ] 14 | environment_variable_aliases = [ 15 | { 16 | key = "ENV_VAR_KEY_ALIAS" 17 | # the value of the alias must be the name of the aliased variable 18 | # e.g here it is an alias to the above declared environment variable "ENV_VAR_KEY" 19 | value = "ENV_VAR_KEY" 20 | } 21 | ] 22 | secrets = [ 23 | { 24 | key = "SECRET_KEY" 25 | value = "SECRET_VALUE" 26 | } 27 | ] 28 | secret_aliases = [ 29 | { 30 | key = "SECRET_KEY_ALIAS" 31 | # the value of the alias must be the name of the aliased secret 32 | # e.g here it is an alias to the above declared secret "SECRET_KEY" 33 | value = "SECRET_KEY" 34 | } 35 | ] 36 | 37 | depends_on = [ 38 | qovery_organization.my_organization 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /internal/domain/git_repository/git_repository.go: -------------------------------------------------------------------------------- 1 | package git_repository 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | var ( 8 | // ErrInvalidURLParam is returned if the URL param is invalid. 9 | ErrInvalidURLParam = errors.New("invalid URL param") 10 | ) 11 | 12 | type GitRepository struct { 13 | Url string 14 | Branch *string 15 | CommitID *string 16 | RootPath *string 17 | GitTokenId *string 18 | } 19 | 20 | func (i GitRepository) Validate() error { 21 | if i.Url == "" { 22 | return ErrInvalidURLParam 23 | } 24 | 25 | return nil 26 | } 27 | 28 | type NewGitRepositoryParams struct { 29 | Url string 30 | Branch *string 31 | CommitID *string 32 | RootPath *string 33 | GitTokenId *string 34 | } 35 | 36 | func NewGitRepository(params NewGitRepositoryParams) (*GitRepository, error) { 37 | gitRepository := &GitRepository{ 38 | Url: params.Url, 39 | Branch: params.Branch, 40 | CommitID: params.CommitID, 41 | RootPath: params.RootPath, 42 | GitTokenId: params.GitTokenId, 43 | } 44 | 45 | if err := gitRepository.Validate(); err != nil { 46 | return nil, err 47 | } 48 | 49 | return gitRepository, nil 50 | } 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | vendor 4 | 5 | terraform-provider-qovery 6 | bin 7 | .idea 8 | .task 9 | .vscode 10 | *.iml 11 | .internal 12 | 13 | # Local .terraform directories 14 | **/.terraform/* 15 | 16 | # AI 17 | CLAUDE.md 18 | .cursor/ 19 | .cursorrules 20 | 21 | # .tfstate files 22 | *.tfstate 23 | *.tfstate.* 24 | 25 | # Crash log files 26 | crash.log 27 | 28 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 29 | # .tfvars files are managed as part of configuration and so should be included in 30 | # version control. 31 | # 32 | # example.tfvars 33 | 34 | # Ignore override files as they are usually used to override resources locally and so 35 | # are not checked in 36 | override.tf 37 | override.tf.json 38 | *_override.tf 39 | *_override.tf.json 40 | 41 | # Include override files you do wish to add to version control using negated pattern 42 | # 43 | # !example_override.tf 44 | 45 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 46 | # example: *tfplan* 47 | 48 | # Ignore CLI configuration files 49 | .terraformrc 50 | terraform.rc 51 | terraform 52 | 53 | # Ignore test output json file 54 | test-output.json -------------------------------------------------------------------------------- /internal/domain/image/test_helper/test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/image" 7 | ) 8 | 9 | var ( 10 | DefaultRegistryID = uuid.New().String() 11 | DefaultName = "image-name" 12 | DefaultTag = "latest" 13 | 14 | /// Exposed to tests needing to get such object without having to know internal sauce magic 15 | DefaultValidNewImageParams = image.NewImageParams{ 16 | RegistryID: DefaultRegistryID, 17 | Name: DefaultName, 18 | Tag: DefaultTag, 19 | } 20 | DefaultValidImage = image.Image{ 21 | RegistryID: DefaultRegistryID, 22 | Name: DefaultName, 23 | Tag: DefaultTag, 24 | } 25 | /// Exposed to tests needing to get such object without having to know internal sauce magic 26 | DefaultInvalidNewImageParams = image.NewImageParams{ 27 | RegistryID: DefaultRegistryID, 28 | Name: "", 29 | Tag: DefaultTag, 30 | } 31 | DefaultInvalidImage = image.Image{ 32 | RegistryID: DefaultRegistryID, 33 | Name: "", 34 | Tag: DefaultTag, 35 | } 36 | DefaultInvalidNewImageParamsError = image.ErrInvalidNameParam 37 | ) 38 | -------------------------------------------------------------------------------- /internal/domain/variable/test_helper/test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "github.com/pkg/errors" 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/variable" 7 | ) 8 | 9 | var ( 10 | DefaultValidVariable = variable.Variable{ 11 | ID: uuid.New(), 12 | Scope: variable.ScopeApplication, 13 | Key: "VariableKey", 14 | Value: "VariableValue", 15 | } 16 | 17 | DefaultValidVariableParams = variable.NewVariableParams{ 18 | VariableID: uuid.New().String(), 19 | Scope: variable.ScopeApplication.String(), 20 | Key: "VariableKey", 21 | Value: "VariableValue", 22 | } 23 | 24 | DefaultInvalidVariable = variable.Variable{ 25 | ID: uuid.New(), 26 | Scope: variable.ScopeApplication, 27 | Key: "", 28 | Value: "VariableValue", 29 | } 30 | 31 | DefaultInvalidVariableParams = variable.NewVariableParams{ 32 | VariableID: uuid.New().String(), 33 | Scope: variable.ScopeApplication.String(), 34 | Key: "", 35 | Value: "VariableValue", 36 | } 37 | 38 | DefaultInvalidVariableParamsError = errors.New("Key: 'Variable.Key' Error:Field validation for 'Key' failed on the 'required' tag") 39 | ) 40 | -------------------------------------------------------------------------------- /internal/domain/credentials/credentials_scaleway.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | var ( 9 | ErrInvalidUpsertScalewayRequest = errors.New("invalid credentials upsert scaleway request") 10 | ) 11 | 12 | // UpsertScalewayRequest represents the parameters needed to create & update Scaleway Credentials. 13 | type UpsertScalewayRequest struct { 14 | Name string `validate:"required"` 15 | ScalewayProjectID string `validate:"required"` 16 | ScalewayAccessKey string `validate:"required"` 17 | ScalewaySecretKey string `validate:"required"` 18 | ScalewayOrganizationID string `validate:"required"` 19 | } 20 | 21 | // Validate returns an error to tell whether the UpsertScalewayRequest is valid or not. 22 | func (r UpsertScalewayRequest) Validate() error { 23 | if err := validator.New().Struct(r); err != nil { 24 | return errors.Wrap(err, ErrInvalidUpsertScalewayRequest.Error()) 25 | } 26 | 27 | return nil 28 | } 29 | 30 | // IsValid returns a bool to tell whether the UpsertScalewayRequest is valid or not. 31 | func (r UpsertScalewayRequest) IsValid() bool { 32 | return r.Validate() == nil 33 | } 34 | -------------------------------------------------------------------------------- /internal/domain/annotations_group/annotations_group.go: -------------------------------------------------------------------------------- 1 | package annotations_group 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "github.com/pkg/errors" 6 | "github.com/qovery/qovery-client-go" 7 | ) 8 | 9 | var ( 10 | ErrInvalidAnnotationsGroupRequest = errors.New("invalid annotations group request") 11 | ErrInvalidAnnotationsGroupOrganizationIdParam = errors.New("invalid organization id format") 12 | ErrFailedToCreateAnnotationsGroup = errors.New("failed to create annotations group") 13 | ErrFailedToGetAnnotationsGroup = errors.New("failed to get annotations group") 14 | ErrFailedToUpdateAnnotationsGroup = errors.New("failed to update annotations group") 15 | ErrFailedToDeleteAnnotationsGroup = errors.New("failed to delete annotations group") 16 | ErrInvalidAnnotationsGroupIdParam = errors.New("invalid annotations group id format") 17 | ErrInvalidScope = errors.New("invalid scope") 18 | ) 19 | 20 | type AnnotationsGroup struct { 21 | Id uuid.UUID `validate:"required"` 22 | Name string 23 | Annotations []qovery.Annotation 24 | Scopes []qovery.OrganizationAnnotationsGroupScopeEnum 25 | } 26 | -------------------------------------------------------------------------------- /client/environment_variables.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/qovery/qovery-client-go" 5 | ) 6 | 7 | type EnvironmentVariablesDiff struct { 8 | Create []EnvironmentVariableCreateRequest 9 | Update []EnvironmentVariableUpdateRequest 10 | Delete []EnvironmentVariableDeleteRequest 11 | } 12 | 13 | func (d EnvironmentVariablesDiff) IsEmpty() bool { 14 | return len(d.Create) == 0 && 15 | len(d.Update) == 0 && 16 | len(d.Delete) == 0 17 | } 18 | 19 | type EnvironmentVariableCreateRequest struct { 20 | qovery.EnvironmentVariableRequest 21 | } 22 | 23 | type EnvironmentVariableUpdateRequest struct { 24 | qovery.EnvironmentVariableEditRequest 25 | Id string 26 | } 27 | 28 | type EnvironmentVariableDeleteRequest struct { 29 | Id string 30 | } 31 | 32 | func environmentVariableResponseListToArray(list *qovery.EnvironmentVariableResponseList, scope qovery.APIVariableScopeEnum) []*qovery.EnvironmentVariable { 33 | vars := make([]*qovery.EnvironmentVariable, 0, len(list.GetResults())) 34 | for _, v := range list.GetResults() { 35 | if v.Scope != scope && v.Scope != qovery.APIVARIABLESCOPEENUM_BUILT_IN { 36 | continue 37 | } 38 | cpy := v 39 | vars = append(vars, &cpy) 40 | } 41 | return vars 42 | } 43 | -------------------------------------------------------------------------------- /internal/infrastructure/repositories/qoveryapi/project_qoveryapi_models.go: -------------------------------------------------------------------------------- 1 | package qoveryapi 2 | 3 | import ( 4 | "github.com/qovery/qovery-client-go" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/project" 7 | "github.com/qovery/terraform-provider-qovery/internal/domain/variable" 8 | ) 9 | 10 | // newDomainCredentialsFromQovery takes a qovery.EnvironmentVariable returned by the API client and turns it into the domain model variable.Variable. 11 | func newDomainProjectFromQovery(p *qovery.Project) (*project.Project, error) { 12 | if p == nil { 13 | return nil, variable.ErrNilVariable 14 | } 15 | 16 | return project.NewProject(project.NewProjectParams{ 17 | ProjectID: p.Id, 18 | OrganizationID: p.Organization.Id, 19 | Name: p.Name, 20 | Description: p.Description, 21 | }) 22 | } 23 | 24 | // newQoveryEnvironmentVariableRequestFromDomain takes the domain request variable.UpsertRequest and turns it into a qovery.EnvironmentVariableRequest to make the api call. 25 | func newQoveryProjectRequestFromDomain(request project.UpsertRepositoryRequest) qovery.ProjectRequest { 26 | return qovery.ProjectRequest{ 27 | Name: request.Name, 28 | Description: request.Description, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/domain/secret/secret_repository.go: -------------------------------------------------------------------------------- 1 | package secret 2 | 3 | //go:generate mockery --testonly --with-expecter --name=Repository --structname=SecretRepository --filename=secret_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/qovery/terraform-provider-qovery/internal/domain/apierrors" 9 | ) 10 | 11 | // Repository represents the interface to implement to handle the persistence of a Secret. 12 | // scopeResourceID can be either a projectID, environmentID, application or containerID 13 | type Repository interface { 14 | Create(ctx context.Context, scopeResourceID string, request UpsertRequest) (*Secret, error) 15 | CreateAlias(ctx context.Context, scopeResourceID string, request UpsertRequest, aliasedSecretId string) (*Secret, error) 16 | CreateOverride(ctx context.Context, scopeResourceID string, request UpsertRequest, overriddenSecretId string) (*Secret, error) 17 | List(ctx context.Context, scopeResourceID string) (Secrets, error) 18 | Update(ctx context.Context, scopeResourceID string, secretID string, request UpsertRequest) (*Secret, error) 19 | Delete(ctx context.Context, scopeResourceID string, secretID string) *apierrors.APIError 20 | } 21 | -------------------------------------------------------------------------------- /qovery/data_source_cluster_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_ClusterDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccClusterDataSourceConfig( 22 | getTestOrganizationID(), 23 | getTestClusterID(), 24 | ), 25 | Check: resource.ComposeAggregateTestCheckFunc( 26 | resource.TestCheckResourceAttr("data.qovery_cluster.test", "id", getTestClusterID()), 27 | resource.TestCheckResourceAttr("data.qovery_cluster.test", "organization_id", getTestOrganizationID()), 28 | ), 29 | }, 30 | }, 31 | }) 32 | } 33 | 34 | func testAccClusterDataSourceConfig(organizationID string, clusterID string) string { 35 | return fmt.Sprintf(` 36 | data "qovery_cluster" "test" { 37 | id = "%s" 38 | organization_id = "%s" 39 | } 40 | `, clusterID, organizationID, 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | 8 | "github.com/hashicorp/terraform-plugin-framework/providerserver" 9 | 10 | "github.com/qovery/terraform-provider-qovery/qovery" 11 | ) 12 | 13 | // Run "go generate" to format example terraform files and generate the docs for the registry/website 14 | 15 | // If you do not have Terraform installed, you can remove the formatting command, but it's suggested to 16 | // ensure the documentation is formatted properly. 17 | //go:generate terraform fmt -recursive ./examples/ 18 | 19 | // Run the documentation generation tool, check its repository for more information on how it works and how docs 20 | // can be customized. 21 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs 22 | 23 | var version = "dev" 24 | 25 | func main() { 26 | var debugMode bool 27 | flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") 28 | flag.Parse() 29 | 30 | opts := providerserver.ServeOpts{ 31 | Address: "registry.terraform.io/Qovery/qovery", 32 | Debug: debugMode, 33 | } 34 | 35 | if err := providerserver.Serve(context.Background(), qovery.New(version), opts); err != nil { 36 | log.Fatal(err) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/domain/variable/variable_repository.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | //go:generate mockery --testonly --with-expecter --name=Repository --structname=VariableRepository --filename=variable_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/qovery/terraform-provider-qovery/internal/domain/apierrors" 9 | ) 10 | 11 | // Repository represents the interface to implement to handle the persistence of a Variable. 12 | // scopeResourceID can be either a projectID, environmentID, application or containerID 13 | type Repository interface { 14 | Create(ctx context.Context, scopeResourceID string, request UpsertRequest) (*Variable, error) 15 | CreateAlias(ctx context.Context, scopeResourceID string, request UpsertRequest, aliasedVariableId string) (*Variable, error) 16 | CreateOverride(ctx context.Context, scopeResourceID string, request UpsertRequest, overriddenVariableId string) (*Variable, error) 17 | List(ctx context.Context, scopeResourceID string) (Variables, error) 18 | Update(ctx context.Context, scopeResourceID string, variableID string, request UpsertRequest) (*Variable, error) 19 | Delete(ctx context.Context, scopeResourceID string, variableID string) *apierrors.APIError 20 | } 21 | -------------------------------------------------------------------------------- /internal/infrastructure/repositories/qoveryapi/organization_qoveryapi_models.go: -------------------------------------------------------------------------------- 1 | package qoveryapi 2 | 3 | import ( 4 | "github.com/qovery/qovery-client-go" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/organization" 7 | ) 8 | 9 | // newDomainOrganizationFromQovery takes a qovery.Organization returned by the API client and turns it into the domain model organization.Organization. 10 | func newDomainOrganizationFromQovery(orga *qovery.Organization) (*organization.Organization, error) { 11 | if orga == nil { 12 | return nil, organization.ErrNilOrganization 13 | } 14 | 15 | return organization.NewOrganization(organization.NewOrganizationParams{ 16 | OrganizationID: orga.GetId(), 17 | Name: orga.GetName(), 18 | Plan: string(orga.Plan), 19 | Description: orga.Description.Get(), 20 | }) 21 | } 22 | 23 | // newQoveryOrganizationEditRequestFromDomain takes the domain request organization.UpdateRequest and turns it into a qovery.OrganizationEditRequest to make the api call. 24 | func newQoveryOrganizationEditRequestFromDomain(request organization.UpdateRequest) qovery.OrganizationEditRequest { 25 | return qovery.OrganizationEditRequest{ 26 | Name: request.Name, 27 | Description: request.Description, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/domain/job/test_helper/job_schedule_test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | execution_command_helper "github.com/qovery/terraform-provider-qovery/internal/domain/execution_command/test_helper" 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/job" 7 | ) 8 | 9 | var ( 10 | DefaultJobSchedule = job.JobSchedule{ 11 | OnStart: &execution_command_helper.DefaultValidExecutionCommand, 12 | OnStop: nil, 13 | OnDelete: nil, 14 | CronJob: nil, 15 | } 16 | 17 | DefaultJobScheduleParams = job.NewJobScheduleParams{ 18 | OnStart: &execution_command_helper.DefaultValidNewExecutionCommandParams, 19 | OnStop: nil, 20 | OnDelete: nil, 21 | CronJob: nil, 22 | } 23 | 24 | DefaultInvalidJobSchedule = job.JobSchedule{ 25 | OnStart: nil, 26 | OnStop: nil, 27 | OnDelete: nil, 28 | CronJob: &DefaultInvalidJobScheduledCronCron, 29 | } 30 | 31 | DefaultInvalidJobScheduleParams = job.NewJobScheduleParams{ 32 | OnStart: nil, 33 | OnStop: nil, 34 | OnDelete: nil, 35 | CronJob: &DefaultInvalidJobScheduledCronCronParams, 36 | } 37 | 38 | DefaultInvalidJobScheduleParamsError = errors.Wrap(DefaultInvalidNewInvalidJobScheduledCronCronParamsError, job.ErrInvalidJobScheduleCronParam.Error()) 39 | ) 40 | -------------------------------------------------------------------------------- /internal/domain/storage/storage_type.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | // Type is an enum that contains all the valid values of a storage type. 10 | type Type string 11 | 12 | const ( 13 | TypeFastSSD Type = "FAST_SSD" 14 | ) 15 | 16 | // AllowedTypeValues contains all the valid values of a Type. 17 | var AllowedTypeValues = []Type{ 18 | TypeFastSSD, 19 | } 20 | 21 | // String returns the string value of a Type. 22 | func (v Type) String() string { 23 | return string(v) 24 | } 25 | 26 | // Validate returns an error to tell whether the Type is valid or not. 27 | func (v Type) Validate() error { 28 | if slices.Contains(AllowedTypeValues, v) { 29 | return nil 30 | } 31 | 32 | return fmt.Errorf("invalid value '%v' for Type: valid values are %v", v, AllowedTypeValues) 33 | } 34 | 35 | // IsValid returns a bool to tell whether the Type is valid or not. 36 | func (v Type) IsValid() bool { 37 | return v.Validate() == nil 38 | } 39 | 40 | // NewTypeFromString tries to turn a string into a Type. 41 | // It returns an error if the string is not a valid value. 42 | func NewTypeFromString(v string) (*Type, error) { 43 | ev := Type(v) 44 | 45 | if err := ev.Validate(); err != nil { 46 | return nil, err 47 | } 48 | 49 | return &ev, nil 50 | } 51 | -------------------------------------------------------------------------------- /client/retry_helper.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/qovery/terraform-provider-qovery/client/apierrors" 7 | ) 8 | 9 | // apiCallFunc is a function that makes an API call and may return an error 10 | type apiCallFunc func(ctx context.Context) *apierrors.APIError 11 | 12 | // retryAPICall wraps an API call with exponential backoff retry logic for transient errors. 13 | // This ensures that temporary network issues (DNS failures, timeouts, EOF, etc.) don't 14 | // cause operations to fail unnecessarily. 15 | // 16 | // The retry logic uses: 17 | // - Up to 3 retry attempts 18 | // - Exponential backoff (2s, 4s, 8s) with jitter 19 | // - Automatic detection of transient vs permanent errors 20 | // 21 | // This helper should be used for all direct API calls that aren't already wrapped 22 | // in a wait function, particularly status check calls that happen outside polling loops. 23 | func retryAPICall(ctx context.Context, f apiCallFunc) *apierrors.APIError { 24 | // Convert apiCallFunc to waitFunc 25 | waitF := func(ctx context.Context) (bool, *apierrors.APIError) { 26 | err := f(ctx) 27 | if err != nil { 28 | return false, err 29 | } 30 | return true, nil 31 | } 32 | 33 | // Use existing retry logic 34 | _, apiErr := retryOnTransientError(ctx, waitF) 35 | return apiErr 36 | } 37 | -------------------------------------------------------------------------------- /internal/domain/helm/helm_values_override.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | type RawValue struct { 4 | Name string 5 | Content string 6 | } 7 | 8 | type Raw struct { 9 | Values []RawValue 10 | } 11 | 12 | type ValuesOverrideGit struct { 13 | Url string 14 | Branch string 15 | Paths []string 16 | GitToken *string 17 | } 18 | 19 | type ValuesOverrideFile struct { 20 | Raw *Raw 21 | GitRepository *ValuesOverrideGit 22 | } 23 | 24 | type ValuesOverride struct { 25 | Set [][]string 26 | SetString [][]string 27 | SetJson [][]string 28 | File *ValuesOverrideFile 29 | } 30 | 31 | type NewHelmValuesOverrideParams struct { 32 | Set [][]string 33 | SetString [][]string 34 | SetJson [][]string 35 | File *ValuesOverrideFile 36 | } 37 | 38 | type NewHelmPortParams struct { 39 | Name string 40 | InternalPort int32 41 | ExternalPort *int32 42 | ServiceName string 43 | Namespace *string 44 | Protocol string 45 | IsDefault bool 46 | } 47 | 48 | func NewHelmValuesOverride(params NewHelmValuesOverrideParams) (*ValuesOverride, error) { 49 | newValuesOverride := &ValuesOverride{ 50 | Set: params.Set, 51 | SetString: params.SetString, 52 | SetJson: params.SetJson, 53 | File: params.File, 54 | } 55 | 56 | return newValuesOverride, nil 57 | } 58 | -------------------------------------------------------------------------------- /internal/domain/image/image.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | ) 6 | 7 | var ( 8 | // ErrInvalidRegistryIDParam is returned if the registry id param is invalid. 9 | ErrInvalidRegistryIDParam = errors.New("invalid registry id param") 10 | // ErrInvalidTagParam is returned if the image tag param is invalid. 11 | ErrInvalidTagParam = errors.New("invalid tag param") 12 | // ErrInvalidNameParam is returned if the name param is invalid. 13 | ErrInvalidNameParam = errors.New("invalid name param") 14 | ) 15 | 16 | type Image struct { 17 | RegistryID string `validate:"required"` 18 | Name string `validate:"required"` 19 | Tag string `validate:"required"` 20 | } 21 | 22 | func (i Image) Validate() error { 23 | if i.Name == "" { 24 | return ErrInvalidNameParam 25 | } 26 | 27 | if i.Tag == "" { 28 | return ErrInvalidTagParam 29 | } 30 | 31 | return nil 32 | } 33 | 34 | type NewImageParams struct { 35 | RegistryID string 36 | Name string 37 | Tag string 38 | } 39 | 40 | func NewImage(params NewImageParams) (*Image, error) { 41 | image := &Image{ 42 | RegistryID: params.RegistryID, 43 | Name: params.Name, 44 | Tag: params.Tag, 45 | } 46 | 47 | if err := image.Validate(); err != nil { 48 | return nil, err 49 | } 50 | 51 | return image, nil 52 | } 53 | -------------------------------------------------------------------------------- /qovery/data_source_organization_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_OrganizationDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccOrganizationDataSourceConfig( 22 | getTestOrganizationID(), 23 | ), 24 | Check: resource.ComposeAggregateTestCheckFunc( 25 | resource.TestCheckResourceAttr("data.qovery_organization.test", "id", getTestOrganizationID()), 26 | resource.TestCheckResourceAttr("data.qovery_organization.test", "name", "Q Sandbox"), 27 | resource.TestCheckResourceAttr("data.qovery_organization.test", "plan", "ENTERPRISE"), 28 | resource.TestCheckResourceAttr("data.qovery_organization.test", "description", "Organization for team's test"), 29 | ), 30 | }, 31 | }, 32 | }) 33 | } 34 | 35 | func testAccOrganizationDataSourceConfig(organizationID string) string { 36 | return fmt.Sprintf(` 37 | data "qovery_organization" "test" { 38 | id = "%s" 39 | } 40 | `, organizationID) 41 | } 42 | -------------------------------------------------------------------------------- /qovery/data_source_aws_credentials_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_AWSCredentialsDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccScalewayCredentialsDataSourceConfig( 22 | getTestAWSCredentialsID(), 23 | getTestOrganizationID(), 24 | ), 25 | Check: resource.ComposeAggregateTestCheckFunc( 26 | resource.TestCheckResourceAttr("data.qovery_aws_credentials.test", "id", getTestAWSCredentialsID()), 27 | resource.TestCheckResourceAttr("data.qovery_aws_credentials.test", "organization_id", getTestOrganizationID()), 28 | resource.TestCheckResourceAttr("data.qovery_aws_credentials.test", "name", "Qovery Sandbox"), 29 | ), 30 | }, 31 | }, 32 | }) 33 | } 34 | 35 | func testAccScalewayCredentialsDataSourceConfig(credentialsID string, organizationID string) string { 36 | return fmt.Sprintf(` 37 | data "qovery_aws_credentials" "test" { 38 | id = "%s" 39 | organization_id = "%s" 40 | } 41 | `, credentialsID, organizationID, 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /internal/domain/docker/test_helper/test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/docker" 7 | git_repository_test_helper "github.com/qovery/terraform-provider-qovery/internal/domain/git_repository/test_helper" 8 | ) 9 | 10 | var ( 11 | DefaultDockerFilePath = "/" 12 | 13 | /// Exposed to tests needing to get such object without having to know internal sauce magic 14 | DefaultValidNewDockerParams = docker.NewDockerParams{ 15 | GitRepository: git_repository_test_helper.DefaultValidNewGitRepositoryParams, 16 | DockerFilePath: &DefaultDockerFilePath, 17 | } 18 | DefaultValidDocker = docker.Docker{ 19 | GitRepository: git_repository_test_helper.DefaultValidGitRepository, 20 | DockerFilePath: &DefaultDockerFilePath, 21 | } 22 | /// Exposed to tests needing to get such object without having to know internal sauce magic 23 | DefaultInvalidNewDockerParams = docker.NewDockerParams{ 24 | GitRepository: git_repository_test_helper.DefaultInvalidNewGitRepositoryParams, 25 | DockerFilePath: nil, 26 | } 27 | DefaultInvalidDocker = docker.Docker{ 28 | GitRepository: git_repository_test_helper.DefaultInvalidGitRepository, 29 | DockerFilePath: nil, 30 | } 31 | DefaultInvalidNewDockerParamsError = errors.Wrap(git_repository_test_helper.DefaultInvalidNewGitRepositoryParamsError, docker.ErrInvalidGitRepositoryParam.Error()) 32 | ) 33 | -------------------------------------------------------------------------------- /internal/domain/job/job_schedule_cron.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | import ( 4 | "github.com/adhocore/gronx" 5 | "github.com/pkg/errors" 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/execution_command" 7 | ) 8 | 9 | var ( 10 | ErrInvalidJobScheduleCronCommandParam = errors.New("invalid `command` param") 11 | ErrInvalidJobScheduleCronScheduleParam = errors.New("invalid `schedule` param") 12 | ) 13 | 14 | type JobScheduleCron struct { 15 | Command execution_command.ExecutionCommand 16 | Schedule string 17 | } 18 | 19 | func (c JobScheduleCron) Validate() error { 20 | gron := gronx.New() 21 | if !gron.IsValid(c.Schedule) { 22 | return errors.Wrap(errors.New("cron string format is invalid"), ErrInvalidJobScheduleCronScheduleParam.Error()) 23 | } 24 | 25 | return nil 26 | } 27 | 28 | type NewJobScheduleCronParams struct { 29 | Command execution_command.NewExecutionCommandParams 30 | Schedule string 31 | } 32 | 33 | func NewJobScheduleCron(params NewJobScheduleCronParams) (*JobScheduleCron, error) { 34 | command, err := execution_command.NewExecutionCommand(params.Command) 35 | if err != nil { 36 | return nil, errors.Wrap(err, ErrInvalidJobScheduleCronCommandParam.Error()) 37 | } 38 | 39 | newJobScheduleCron := &JobScheduleCron{ 40 | Command: *command, 41 | Schedule: params.Schedule, 42 | } 43 | 44 | if err := newJobScheduleCron.Validate(); err != nil { 45 | return nil, err 46 | } 47 | 48 | return newJobScheduleCron, nil 49 | } 50 | -------------------------------------------------------------------------------- /docs/resources/annotations_group.md: -------------------------------------------------------------------------------- 1 | # qovery_annotations_group (Resource) 2 | 3 | Provides a Qovery annotations group resource 4 | 5 | 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | ```terraform 13 | resource "qovery_annotations_group" "annotations_group1" { 14 | organization_id = qovery_organization.my_organization.id 15 | name = "MyAnnotationsGroup" 16 | annotations = { 17 | "key1" = "value1" 18 | "key2" = "value2" 19 | } 20 | scopes = ["PODS", "DEPLOYMENTS"] 21 | } 22 | ``` 23 | 24 | 25 | ## Schema 26 | 27 | ### Required 28 | 29 | - `annotations` (Map of String) annotations 30 | - `name` (String) name of the annotations group 31 | - `organization_id` (String) Id of the organization. 32 | - `scopes` (Set of String) scopes of the annotations group 33 | 34 | ### Read-Only 35 | 36 | - `id` (String) Id of the annotations group 37 | ## Import 38 | ```shell 39 | terraform import qovery_annotations_group.my_qovery_annotations_group "" 40 | ``` -------------------------------------------------------------------------------- /internal/domain/job/test_helper/job_scheduled_cron_test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | execution_command_test_helper "github.com/qovery/terraform-provider-qovery/internal/domain/execution_command/test_helper" 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/job" 7 | ) 8 | 9 | var ( 10 | DefaultJobScheduledCronCronString = "*/30 * * * *" 11 | DefaultJobScheduledCronInvalidCronString = "" 12 | DefaultValidJobScheduledCronCronParams = job.NewJobScheduleCronParams{ 13 | Command: execution_command_test_helper.DefaultValidNewExecutionCommandParams, 14 | Schedule: DefaultJobScheduledCronCronString, 15 | } 16 | DefaultValidJobScheduledCronCron = job.JobScheduleCron{ 17 | Command: execution_command_test_helper.DefaultValidExecutionCommand, 18 | Schedule: DefaultJobScheduledCronCronString, 19 | } 20 | DefaultInvalidJobScheduledCronCronParams = job.NewJobScheduleCronParams{ 21 | Command: execution_command_test_helper.DefaultValidNewExecutionCommandParams, 22 | Schedule: DefaultJobScheduledCronInvalidCronString, 23 | } 24 | DefaultInvalidJobScheduledCronCron = job.JobScheduleCron{ 25 | Command: execution_command_test_helper.DefaultValidExecutionCommand, 26 | Schedule: DefaultJobScheduledCronInvalidCronString, 27 | } 28 | DefaultInvalidNewInvalidJobScheduledCronCronParamsError = errors.Wrap(errors.New("cron string format is invalid"), job.ErrInvalidJobScheduleCronScheduleParam.Error()) 29 | ) 30 | -------------------------------------------------------------------------------- /qovery/data_source_scaleway_credentials_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_ScalewayCredentialsDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccAwsCredentialsDataSourceConfig( 22 | getTestScalewayCredentialsID(), 23 | getTestOrganizationID(), 24 | ), 25 | Check: resource.ComposeAggregateTestCheckFunc( 26 | resource.TestCheckResourceAttr("data.qovery_scaleway_credentials.test", "id", getTestScalewayCredentialsID()), 27 | resource.TestCheckResourceAttr("data.qovery_scaleway_credentials.test", "organization_id", getTestOrganizationID()), 28 | resource.TestCheckResourceAttr("data.qovery_scaleway_credentials.test", "name", "terraform-provider-test-scaleway"), 29 | ), 30 | }, 31 | }, 32 | }) 33 | } 34 | 35 | func testAccAwsCredentialsDataSourceConfig(credentialsID string, organizationID string) string { 36 | return fmt.Sprintf(` 37 | data "qovery_scaleway_credentials" "test" { 38 | id = "%s" 39 | organization_id = "%s" 40 | } 41 | `, credentialsID, organizationID, 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /internal/domain/deployment/deployment_service.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | 8 | "github.com/qovery/terraform-provider-qovery/internal/domain/status" 9 | ) 10 | 11 | //go:generate mockery --testonly --with-expecter --name=Service --structname=DeploymentService --filename=deployment_service_mock.go --output=../../application/services/mocks_test/ --outpkg=mocks_test 12 | var ( 13 | ErrInvalidResourceIDParam = errors.New("invalid resource id param") 14 | ErrUnexpectedState = errors.New("unexpected state") 15 | ErrFailedToGetStatus = errors.New("failed to get status") 16 | ErrFailedToUpdateState = errors.New("failed to update state") 17 | ErrFailedToDeploy = errors.New("failed to deploy") 18 | ErrFailedToRedeploy = errors.New("failed to redeploy") 19 | ErrFailedToStop = errors.New("failed to stop") 20 | ) 21 | 22 | // Service represents the interface to implement to handle the domain logic of a deployment. 23 | type Service interface { 24 | GetStatus(ctx context.Context, resourceID string) (*status.Status, error) 25 | UpdateState(ctx context.Context, resourceID string, desiredState status.State, version string) (*status.Status, error) 26 | Deploy(ctx context.Context, resourceID string, version string) (*status.Status, error) 27 | Redeploy(ctx context.Context, resourceID string) (*status.Status, error) 28 | Stop(ctx context.Context, resourceID string) (*status.Status, error) 29 | } 30 | -------------------------------------------------------------------------------- /internal/domain/environment/environment_mode.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | // Mode is an enum that contains all the valid values of an environment plan. 10 | type Mode string 11 | 12 | const ( 13 | ModeDevelopment Mode = "DEVELOPMENT" 14 | ModePreview Mode = "PREVIEW" 15 | ModeProduction Mode = "PRODUCTION" 16 | ModeStaging Mode = "STAGING" 17 | ) 18 | 19 | // AllowedModeValues contains all the valid values of a Mode. 20 | var AllowedModeValues = []Mode{ 21 | ModeDevelopment, 22 | ModePreview, 23 | ModeProduction, 24 | ModeStaging, 25 | } 26 | 27 | // String returns the string value of a Mode. 28 | func (v Mode) String() string { 29 | return string(v) 30 | } 31 | 32 | // Validate returns an error to tell whether the Mode is valid or not. 33 | func (v Mode) Validate() error { 34 | if slices.Contains(AllowedModeValues, v) { 35 | return nil 36 | } 37 | 38 | return fmt.Errorf("invalid value '%v' for Mode: valid values are %v", v, AllowedModeValues) 39 | } 40 | 41 | // IsValid returns a bool to tell whether the Mode is valid or not. 42 | func (v Mode) IsValid() bool { 43 | return v.Validate() == nil 44 | } 45 | 46 | // NewModeFromString tries to turn a string into a Mode. 47 | // It returns an error if the string is not a valid value. 48 | func NewModeFromString(v string) (*Mode, error) { 49 | ev := Mode(v) 50 | 51 | if err := ev.Validate(); err != nil { 52 | return nil, err 53 | } 54 | 55 | return &ev, nil 56 | } 57 | -------------------------------------------------------------------------------- /docs/resources/organization.md: -------------------------------------------------------------------------------- 1 | # qovery_organization (Resource) 2 | 3 | Provides a Qovery organization resource. This can be used to create and manage Qovery organizations. 4 | 5 | 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | ```terraform 13 | resource "qovery_organization" "my_organization" { 14 | # Required 15 | name = "MyOrganization" 16 | plan = "FREE" 17 | 18 | # Optional 19 | description = "My organization description" 20 | } 21 | ``` 22 | 23 | 24 | ## Schema 25 | 26 | ### Required 27 | 28 | - `name` (String) Name of the organization. 29 | - `plan` (String) Plan of the organization. 30 | - Can be: `BUSINESS`, `BUSINESS_2025`, `ENTERPRISE`, `ENTERPRISE_2025`, `ENTERPRISE_YEARLY`, `FREE`, `PROFESSIONAL`, `TEAM`, `TEAM_2025`, `TEAM_YEARLY`, `USER_2025`. 31 | 32 | ### Optional 33 | 34 | - `description` (String) Description of the organization. 35 | 36 | ### Read-Only 37 | 38 | - `id` (String) Id of the organization. 39 | ## Import 40 | ```shell 41 | terraform import qovery_organization.my_organization "" 42 | ``` -------------------------------------------------------------------------------- /internal/infrastructure/repositories/qoveryapi/status_qoveryapi_models_test.go: -------------------------------------------------------------------------------- 1 | package qoveryapi 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/AlekSi/pointer" 7 | "github.com/brianvoe/gofakeit/v6" 8 | "github.com/qovery/qovery-client-go" 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/qovery/terraform-provider-qovery/internal/domain/status" 12 | ) 13 | 14 | func TestNewDomainStatusFromQovery(t *testing.T) { 15 | t.Parallel() 16 | 17 | testCases := []struct { 18 | TestName string 19 | Status *qovery.Status 20 | ExpectedError error 21 | }{ 22 | { 23 | TestName: "fail_with_nil_container", 24 | ExpectedError: status.ErrNilStatus, 25 | }, 26 | { 27 | TestName: "success", 28 | Status: &qovery.Status{ 29 | Id: gofakeit.UUID(), 30 | State: qovery.STATEENUM_DEPLOYED, 31 | LastDeploymentDate: pointer.ToTime(gofakeit.Date()), 32 | }, 33 | }, 34 | } 35 | 36 | for _, tc := range testCases { 37 | tc := tc 38 | t.Run(tc.TestName, func(t *testing.T) { 39 | st, err := newDomainStatusFromQovery(tc.Status) 40 | if tc.ExpectedError != nil { 41 | assert.ErrorContains(t, err, tc.ExpectedError.Error()) 42 | assert.Nil(t, st) 43 | return 44 | } 45 | 46 | assert.NoError(t, err) 47 | assert.NotNil(t, st) 48 | assert.Equal(t, tc.Status.Id, st.ID.String()) 49 | assert.Equal(t, string(tc.Status.State), st.State.String()) 50 | assert.Equal(t, tc.Status.LastDeploymentDate, st.LastDeploymentDate) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/domain/port/port_protocol.go: -------------------------------------------------------------------------------- 1 | package port 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | // Protocol is an enum that contains all the valid values of a port protocol. 10 | type Protocol string 11 | 12 | const ( 13 | ProtocolHTTP Protocol = "HTTP" 14 | ProtocolGRPC Protocol = "GRPC" 15 | ProtocolTCP Protocol = "TCP" 16 | ProtocolUDP Protocol = "UDP" 17 | ) 18 | 19 | // AllowedProtocolValues contains all the valid values of a Protocol. 20 | var AllowedProtocolValues = []Protocol{ 21 | ProtocolHTTP, 22 | ProtocolGRPC, 23 | ProtocolTCP, 24 | ProtocolUDP, 25 | } 26 | 27 | // String returns the string value of a Protocol. 28 | func (v Protocol) String() string { 29 | return string(v) 30 | } 31 | 32 | // Validate returns an error to tell whether the Protocol is valid or not. 33 | func (v Protocol) Validate() error { 34 | if slices.Contains(AllowedProtocolValues, v) { 35 | return nil 36 | } 37 | 38 | return fmt.Errorf("invalid value '%v' for Protocol: valid values are %v", v, AllowedProtocolValues) 39 | } 40 | 41 | // IsValid returns a bool to tell whether the Protocol is valid or not. 42 | func (v Protocol) IsValid() bool { 43 | return v.Validate() == nil 44 | } 45 | 46 | // NewProtocolFromString tries to turn a string into a Protocol. 47 | // It returns an error if the string is not a valid value. 48 | func NewProtocolFromString(v string) (*Protocol, error) { 49 | ev := Protocol(v) 50 | 51 | if err := ev.Validate(); err != nil { 52 | return nil, err 53 | } 54 | 55 | return &ev, nil 56 | } 57 | -------------------------------------------------------------------------------- /docs/resources/deployment.md: -------------------------------------------------------------------------------- 1 | # qovery_deployment (Resource) 2 | 3 | Provides a Qovery deployment resource. This is used to trigger a service deployment at demand. 4 | 5 | 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | ```terraform 13 | resource "qovery_deployment" "my_deployment" { 14 | # Required 15 | environment_id = qovery_environment.my_environment.id 16 | desired_state = "RUNNING" 17 | version = "random_uuid_to_force_retrigger_terraform_apply" 18 | 19 | depends_on = [ 20 | qovery_application.my_application, 21 | qovery_database.my_database, 22 | qovery_container.my_container, 23 | ] 24 | } 25 | ``` 26 | 27 | 28 | ## Schema 29 | 30 | ### Required 31 | 32 | - `desired_state` (String) Desired state of the deployment. 33 | - Can be: `RESTARTED`, `RUNNING`, `STOPPED`. 34 | - `environment_id` (String) Id of the environment. 35 | 36 | ### Optional 37 | 38 | - `id` (String) Id of the deployment 39 | - `version` (String) Version to force trigger a deployment when desired_state doesn't change (e.g redeploy a deployment having the 'RUNNING' state) 40 | -------------------------------------------------------------------------------- /internal/domain/deploymentstage/deployment_stage_repository.go: -------------------------------------------------------------------------------- 1 | package deploymentstage 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-playground/validator/v10" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // Repository represents the interface to implement to handle the persistence of a DeploymentStage. 11 | type Repository interface { 12 | Create(ctx context.Context, environmentID string, request UpsertRepositoryRequest) (*DeploymentStage, error) 13 | Get(ctx context.Context, environmentID string, deploymentStageID string) (*DeploymentStage, error) 14 | GetAllByEnvironmentID(ctx context.Context, environmentID string) (*[]DeploymentStage, error) 15 | Update(ctx context.Context, deploymentStageID string, request UpsertRepositoryRequest) (*DeploymentStage, error) 16 | Delete(ctx context.Context, deploymentStageID string) error 17 | } 18 | 19 | // UpsertRepositoryRequest represents the parameters needed to create & update a DeploymentStage 20 | type UpsertRepositoryRequest struct { 21 | Name string `validate:"required"` 22 | Description string 23 | IsAfter *string 24 | IsBefore *string 25 | } 26 | 27 | // Validate returns an error to tell whether the UpsertRepositoryRequest is valid or not. 28 | func (r UpsertRepositoryRequest) Validate() error { 29 | if err := validator.New().Struct(r); err != nil { 30 | return errors.Wrap(err, ErrInvalidUpsertRequest.Error()) 31 | } 32 | 33 | return nil 34 | } 35 | 36 | // IsValid returns a bool to tell whether the UpsertRepositoryRequest is valid or not. 37 | func (r UpsertRepositoryRequest) IsValid() bool { 38 | return r.Validate() == nil 39 | } 40 | -------------------------------------------------------------------------------- /internal/domain/docker/docker.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/git_repository" 7 | ) 8 | 9 | var ( 10 | // ErrInvalidGitRepositoryParam is returned if the git repository param is invalid. 11 | ErrInvalidGitRepositoryParam = errors.New("invalid git repository param") 12 | ) 13 | 14 | type Docker struct { 15 | GitRepository git_repository.GitRepository 16 | DockerFilePath *string 17 | DockerFileRaw *string 18 | DockerTargetBuildStage *string 19 | } 20 | 21 | func (d Docker) Validate() error { 22 | if err := d.GitRepository.Validate(); err != nil { 23 | return errors.Wrap(err, ErrInvalidGitRepositoryParam.Error()) 24 | } 25 | 26 | return nil 27 | } 28 | 29 | type NewDockerParams struct { 30 | GitRepository git_repository.NewGitRepositoryParams 31 | DockerFilePath *string 32 | DockerFileRaw *string 33 | DockerTargetBuildStage *string 34 | } 35 | 36 | func NewDocker(params NewDockerParams) (*Docker, error) { 37 | gitRepository, err := git_repository.NewGitRepository(params.GitRepository) 38 | if err != nil { 39 | return nil, errors.Wrap(err, ErrInvalidGitRepositoryParam.Error()) 40 | } 41 | docker := &Docker{ 42 | GitRepository: *gitRepository, 43 | DockerFilePath: params.DockerFilePath, 44 | DockerFileRaw: params.DockerFileRaw, 45 | DockerTargetBuildStage: params.DockerTargetBuildStage, 46 | } 47 | 48 | if err := docker.Validate(); err != nil { 49 | return nil, err 50 | } 51 | 52 | return docker, nil 53 | } 54 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Terraform Provider release workflow. 2 | # Sources: https://github.com/hashicorp/terraform-provider-scaffolding-framework/blob/main/.github/workflows/release.yml 3 | name: release 4 | 5 | # This GitHub action creates a release when a tag that matches the pattern 6 | # "v*" (e.g. v0.1.0) is created. 7 | on: 8 | push: 9 | tags: 10 | - 'v*' 11 | 12 | # Releases need permissions to read and write the repository contents. 13 | # GitHub considers creating releases and uploading assets as writing contents. 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | goreleaser: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | with: 24 | # Allow goreleaser to access older tag information. 25 | fetch-depth: 0 26 | 27 | - name: Set up Go 28 | uses: actions/setup-go@v3 29 | with: 30 | go-version-file: 'go.mod' 31 | cache: true 32 | 33 | - name: Import GPG key 34 | id: import_gpg 35 | uses: crazy-max/ghaction-import-gpg@v5 36 | with: 37 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 38 | passphrase: ${{ secrets.PASSPHRASE }} 39 | 40 | - name: Run GoReleaser 41 | uses: goreleaser/goreleaser-action@v6 42 | with: 43 | version: 'v1.26.2' 44 | args: release --rm-dist 45 | env: 46 | # GitHub sets the GITHUB_TOKEN secret automatically. 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 49 | -------------------------------------------------------------------------------- /qovery/resource_git_token_models.go: -------------------------------------------------------------------------------- 1 | package qovery 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/types" 5 | "github.com/qovery/qovery-client-go" 6 | 7 | "github.com/qovery/terraform-provider-qovery/internal/domain/gittoken" 8 | ) 9 | 10 | type GitToken struct { 11 | ID types.String `tfsdk:"id"` 12 | OrganizationId types.String `tfsdk:"organization_id"` 13 | Name types.String `tfsdk:"name"` 14 | Description types.String `tfsdk:"description"` 15 | Type types.String `tfsdk:"type"` 16 | Token types.String `tfsdk:"token"` 17 | BitbucketWorkspace types.String `tfsdk:"bitbucket_workspace"` 18 | } 19 | 20 | func (it GitToken) toUpsertRequest() gittoken.GitTokenParams { 21 | return gittoken.GitTokenParams{ 22 | Name: ToString(it.Name), 23 | Description: ToStringPointer(it.Description), 24 | Type: ToString(it.Type), 25 | Token: ToString(it.Token), 26 | BitbucketWorkspace: ToStringPointer(it.BitbucketWorkspace), 27 | } 28 | } 29 | 30 | func toTerraformObject(organizationID string, token string, gitTokenResponse qovery.GitTokenResponse) GitToken { 31 | return GitToken{ 32 | ID: FromString(gitTokenResponse.Id), 33 | OrganizationId: FromString(organizationID), 34 | Name: FromString(gitTokenResponse.Name), 35 | Description: FromStringPointer(gitTokenResponse.Description), 36 | Type: FromString(string(gitTokenResponse.Type)), 37 | Token: FromString(token), 38 | BitbucketWorkspace: FromStringPointer(gitTokenResponse.Workspace), 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/domain/project/project_repository.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | //go:generate mockery --testonly --with-expecter --name=Repository --structname=ProjectRepository --filename=project_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/go-playground/validator/v10" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // Repository represents the interface to implement to handle the persistence of a Project. 13 | // projectID can be either a projectID, environmentID, application or containerID 14 | type Repository interface { 15 | Create(ctx context.Context, organizationID string, request UpsertRepositoryRequest) (*Project, error) 16 | Get(ctx context.Context, projectID string) (*Project, error) 17 | Update(ctx context.Context, projectID string, request UpsertRepositoryRequest) (*Project, error) 18 | Delete(ctx context.Context, projectID string) error 19 | } 20 | 21 | // UpsertRepositoryRequest represents the parameters needed to create & update a Variable. 22 | type UpsertRepositoryRequest struct { 23 | Name string `validate:"required"` 24 | Description *string 25 | } 26 | 27 | // Validate returns an error to tell whether the UpsertRepositoryRequest is valid or not. 28 | func (r UpsertRepositoryRequest) Validate() error { 29 | if err := validator.New().Struct(r); err != nil { 30 | return errors.Wrap(err, ErrInvalidUpsertRequest.Error()) 31 | } 32 | 33 | return nil 34 | } 35 | 36 | // IsValid returns a bool to tell whether the UpsertRepositoryRequest is valid or not. 37 | func (r UpsertRepositoryRequest) IsValid() bool { 38 | return r.Validate() == nil 39 | } 40 | -------------------------------------------------------------------------------- /internal/domain/git_repository/test_helper/test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/qovery/terraform-provider-qovery/internal/domain/git_repository" 5 | ) 6 | 7 | var ( 8 | DefaultUrl = "https://github.com/Qovery/terraform-provider-qovery.git" 9 | DefaultBranchName = "main" 10 | DefaultCommitID = "42e2e5af9d49de268cd1fda3587788da4ace418a" 11 | DefaultRootPath = "/" 12 | 13 | /// Exposed to tests needing to get such object without having to know internal sauce magic 14 | DefaultValidNewGitRepositoryParams = git_repository.NewGitRepositoryParams{ 15 | Url: DefaultUrl, 16 | Branch: &DefaultBranchName, 17 | CommitID: &DefaultCommitID, 18 | RootPath: &DefaultRootPath, 19 | } 20 | DefaultValidGitRepository = git_repository.GitRepository{ 21 | Url: DefaultValidNewGitRepositoryParams.Url, 22 | Branch: DefaultValidNewGitRepositoryParams.Branch, 23 | CommitID: DefaultValidNewGitRepositoryParams.CommitID, 24 | RootPath: DefaultValidNewGitRepositoryParams.RootPath, 25 | } 26 | /// Exposed to tests needing to get such object without having to know internal sauce magic 27 | DefaultInvalidNewGitRepositoryParams = git_repository.NewGitRepositoryParams{ 28 | Url: "", 29 | Branch: nil, 30 | CommitID: nil, 31 | RootPath: nil, 32 | } 33 | DefaultInvalidGitRepository = git_repository.GitRepository{ 34 | Url: DefaultInvalidNewGitRepositoryParams.Url, 35 | Branch: DefaultInvalidNewGitRepositoryParams.Branch, 36 | CommitID: DefaultInvalidNewGitRepositoryParams.CommitID, 37 | RootPath: DefaultInvalidNewGitRepositoryParams.RootPath, 38 | } 39 | DefaultInvalidNewGitRepositoryParamsError = git_repository.ErrInvalidURLParam 40 | ) 41 | -------------------------------------------------------------------------------- /qovery/data_source_container_registry_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_ContainerRegistryDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccContainerRegistryDataSourceConfig( 22 | getTestContainerRegistryID(), 23 | getTestOrganizationID(), 24 | ), 25 | Check: resource.ComposeAggregateTestCheckFunc( 26 | resource.TestCheckResourceAttr("data.qovery_container_registry.test", "organization_id", getTestOrganizationID()), 27 | resource.TestCheckResourceAttr("data.qovery_container_registry.test", "name", "Terraform Provider Tests"), 28 | resource.TestCheckResourceAttr("data.qovery_container_registry.test", "kind", "ECR"), 29 | resource.TestCheckResourceAttr("data.qovery_container_registry.test", "url", "https://default.com"), 30 | resource.TestCheckResourceAttr("data.qovery_container_registry.test", "description", "Container Registry used to run test for our terraform provider"), 31 | ), 32 | }, 33 | }, 34 | }) 35 | } 36 | 37 | func testAccContainerRegistryDataSourceConfig(containerRegistryID string, organizationID string) string { 38 | return fmt.Sprintf(` 39 | data "qovery_container_registry" "test" { 40 | id = "%s" 41 | organization_id = "%s" 42 | } 43 | `, containerRegistryID, organizationID, 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /internal/domain/variable/variable_scope.go: -------------------------------------------------------------------------------- 1 | package variable 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | // Scope is an enum that contains all the valid values of a variable scope. 10 | type Scope string 11 | 12 | const ( 13 | ScopeApplication Scope = "APPLICATION" 14 | ScopeBuiltIn Scope = "BUILT_IN" 15 | ScopeContainer Scope = "CONTAINER" 16 | ScopeEnvironment Scope = "ENVIRONMENT" 17 | ScopeProject Scope = "PROJECT" 18 | ScopeJob Scope = "JOB" 19 | ScopeHelm Scope = "HELM" 20 | ScopeTerraform Scope = "TERRAFORM" 21 | ) 22 | 23 | // AllowedScopeValues contains all the valid values of a Scope. 24 | var AllowedScopeValues = []Scope{ 25 | ScopeApplication, 26 | ScopeBuiltIn, 27 | ScopeContainer, 28 | ScopeEnvironment, 29 | ScopeProject, 30 | ScopeJob, 31 | ScopeHelm, 32 | ScopeTerraform, 33 | } 34 | 35 | // String returns the string value of a Scope. 36 | func (v Scope) String() string { 37 | return string(v) 38 | } 39 | 40 | // Validate returns an error to tell whether the Scope is valid or not. 41 | func (v Scope) Validate() error { 42 | if slices.Contains(AllowedScopeValues, v) { 43 | return nil 44 | } 45 | 46 | return fmt.Errorf("invalid value '%v' for Scope: valid values are %v", v, AllowedScopeValues) 47 | } 48 | 49 | // IsValid returns a bool to tell whether the Scope is valid or not. 50 | func (v Scope) IsValid() bool { 51 | return v.Validate() == nil 52 | } 53 | 54 | // NewScopeFromString tries to turn a string into a Scope. 55 | // It returns an error if the string is not a valid value. 56 | func NewScopeFromString(v string) (*Scope, error) { 57 | ev := Scope(v) 58 | 59 | if err := ev.Validate(); err != nil { 60 | return nil, err 61 | } 62 | 63 | return &ev, nil 64 | } 65 | -------------------------------------------------------------------------------- /docs/resources/aws_credentials.md: -------------------------------------------------------------------------------- 1 | # qovery_aws_credentials (Resource) 2 | 3 | Provides a Qovery AWS credentials resource. This can be used to create and manage Qovery AWS credentials. 4 | 5 | 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | ```terraform 13 | resource "qovery_aws_credentials" "my_aws_creds" { 14 | # Required 15 | organization_id = qovery_organization.my_organization.id 16 | name = "my_aws_creds" 17 | access_key_id = "" 18 | secret_access_key = "" 19 | 20 | depends_on = [ 21 | qovery_organization.my_organization 22 | ] 23 | } 24 | ``` 25 | 26 | 27 | ## Schema 28 | 29 | ### Required 30 | 31 | - `name` (String) Name of the aws credentials. 32 | - `organization_id` (String) Id of the organization. 33 | 34 | ### Optional 35 | 36 | - `access_key_id` (String) Your AWS access key id. 37 | - `role_arn` (String) Your AWS role that you want Qovery to assume. You can't specify access/secret_key if you use a role 38 | - `secret_access_key` (String, Sensitive) Your AWS secret access key. 39 | 40 | ### Read-Only 41 | 42 | - `id` (String) Id of the AWS credentials. 43 | ## Import 44 | ```shell 45 | terraform import qovery_aws_credentials.my_aws_creds "," 46 | ``` -------------------------------------------------------------------------------- /docs/resources/labels_group.md: -------------------------------------------------------------------------------- 1 | # qovery_labels_group (Resource) 2 | 3 | Provides a Qovery labels group resource 4 | 5 | 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | ```terraform 13 | resource "qovery_labels_group" "labels_group1" { 14 | organization_id = qovery_organization.my_organization.id 15 | name = "MyLabelsGroup" 16 | labels = [ 17 | { 18 | key = "key1" 19 | value = "value1" 20 | propagate_to_cloud_provider = false 21 | }, 22 | { 23 | key = "key2" 24 | value = "value2" 25 | propagate_to_cloud_provider = true 26 | } 27 | ] 28 | } 29 | ``` 30 | 31 | 32 | ## Schema 33 | 34 | ### Required 35 | 36 | - `labels` (Attributes Set) labels (see [below for nested schema](#nestedatt--labels)) 37 | - `name` (String) name of the labels group 38 | - `organization_id` (String) Id of the organization. 39 | 40 | ### Read-Only 41 | 42 | - `id` (String) Id of the labels group 43 | 44 | 45 | ### Nested Schema for `labels` 46 | 47 | Required: 48 | 49 | - `key` (String) 50 | - `propagate_to_cloud_provider` (Boolean) 51 | - `value` (String) 52 | ## Import 53 | ```shell 54 | terraform import qovery_labels_group.my_qovery_labels_group "" 55 | ``` -------------------------------------------------------------------------------- /internal/domain/helmRepository/helm_repository_kind.go: -------------------------------------------------------------------------------- 1 | package helmRepository 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | // Kind is an enum that contains all the valid values of a registry kind. 10 | type Kind string 11 | 12 | const ( 13 | KindHttps Kind = "HTTPS" 14 | KindECR Kind = "OCI_ECR" 15 | KindDocker Kind = "OCI_DOCR" 16 | KindScalewayCR Kind = "OCI_SCALEWAY_CR" 17 | KindDockerHub Kind = "OCI_DOCKER_HUB" 18 | KindGithubCr Kind = "OCI_GITHUB_CR" 19 | KindGitlabCr Kind = "OCI_GITLAB_CR" 20 | KindPublicECR Kind = "OCI_PUBLIC_ECR" 21 | KindGenericCR Kind = "OCI_GENERIC_CR" 22 | ) 23 | 24 | // AllowedKindValues contains all the valid values of a Kind. 25 | var AllowedKindValues = []Kind{ 26 | KindHttps, 27 | KindECR, 28 | KindDocker, 29 | KindScalewayCR, 30 | KindDockerHub, 31 | KindGithubCr, 32 | KindGitlabCr, 33 | KindPublicECR, 34 | KindGenericCR, 35 | } 36 | 37 | // String returns the string value of a Kind. 38 | func (v Kind) String() string { 39 | return string(v) 40 | } 41 | 42 | // Validate returns an error to tell whether the Kind is valid or not. 43 | func (v Kind) Validate() error { 44 | if slices.Contains(AllowedKindValues, v) { 45 | return nil 46 | } 47 | 48 | return fmt.Errorf("invalid value '%v' for Kind: valid values are %v", v, AllowedKindValues) 49 | } 50 | 51 | // IsValid returns a bool to tell whether the Kind is valid or not. 52 | func (v Kind) IsValid() bool { 53 | return v.Validate() == nil 54 | } 55 | 56 | // NewKindFromString tries to turn a string into a Kind. 57 | // It returns an error if the string is not a valid value. 58 | func NewKindFromString(v string) (*Kind, error) { 59 | ev := Kind(v) 60 | 61 | if err := ev.Validate(); err != nil { 62 | return nil, err 63 | } 64 | 65 | return &ev, nil 66 | } 67 | -------------------------------------------------------------------------------- /examples/resources/qovery_environment/resource.tf: -------------------------------------------------------------------------------- 1 | resource "qovery_environment" "my_environment" { 2 | # Required 3 | project_id = qovery_project.my_project.id 4 | name = "MyEnvironment" 5 | 6 | # Optional 7 | cluster_id = qovery_cluster.my_cluster.id 8 | mode = "DEVELOPMENT" 9 | environment_variables = [ 10 | { 11 | key = "ENV_VAR_KEY" 12 | value = "ENV_VAR_VALUE" 13 | } 14 | ] 15 | environment_variable_aliases = [ 16 | { 17 | key = "ENV_VAR_KEY_ALIAS" 18 | # the value of the alias must be the name of the aliased variable 19 | # e.g here it is an alias to the above declared environment variable "ENV_VAR_KEY" 20 | value = "ENV_VAR_KEY" 21 | } 22 | ] 23 | environment_variable_overrides = [ 24 | { 25 | # the key of the override must be the name of the overridden variable 26 | # e.g here it is an override on a variable declared at project scope "SOME_PROJECT_VARIABLE" 27 | key = "SOME_PROJECT_VARIABLE" 28 | value = "OVERRIDDEN_VALUE" 29 | } 30 | ] 31 | secrets = [ 32 | { 33 | key = "SECRET_KEY" 34 | value = "SECRET_VALUE" 35 | } 36 | ] 37 | secret_aliases = [ 38 | { 39 | key = "SECRET_KEY_ALIAS" 40 | # the value of the alias must be the name of the aliased secret 41 | # e.g here it is an alias to the above declared secret "SECRET_KEY" 42 | value = "SECRET_KEY" 43 | } 44 | ] 45 | secret_overrides = [ 46 | { 47 | # the key of the override must be the name of the overridden secret 48 | # e.g here it is an override on a secret declared at project scope "SOME_PROJECT_SECRET" 49 | key = "SOME_PROJECT_SECRET" 50 | value = "OVERRIDDEN_VALUE" 51 | } 52 | ] 53 | 54 | depends_on = [ 55 | qovery_project.my_project 56 | ] 57 | } -------------------------------------------------------------------------------- /docs/resources/git_token.md: -------------------------------------------------------------------------------- 1 | # qovery_git_token (Resource) 2 | 3 | Provides a Qovery git token resource. This can be used to create and manage Qovery git token. 4 | 5 | 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | ```terraform 13 | resource "qovery_git_token" "my_git_token" { 14 | # Required 15 | organization_id = qovery_organization.my_organization.id 16 | name = "my-git-token" 17 | type = "GITHUB" 18 | token = "my-git-provider-token" 19 | 20 | # Optional 21 | description = "Github token" 22 | 23 | # Only necessary for BITBUCKET git tokens 24 | bitbucket_workspace = "workspace-bitbucket" 25 | } 26 | ``` 27 | 28 | 29 | ## Schema 30 | 31 | ### Required 32 | 33 | - `name` (String) Name of the git token. 34 | - `organization_id` (String) Id of the organization. 35 | - `token` (String, Sensitive) Value of the git token. 36 | - `type` (String) Type of the git token. 37 | - Can be: `BITBUCKET`, `GITHUB`, `GITLAB`. 38 | 39 | ### Optional 40 | 41 | - `bitbucket_workspace` (String) (Mandatory only for Bitbucket git token) Workspace where the token has permissions . 42 | - `description` (String) Description of the git token. 43 | 44 | ### Read-Only 45 | 46 | - `id` (String) Id of the git token. 47 | ## Import 48 | ```shell 49 | terraform import qovery_git_token.my_git_token "," 50 | ``` -------------------------------------------------------------------------------- /qovery/data_source_helm_repository_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_HelmRepositoryDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccHelmRepositoryDataSourceConfig( 22 | getTestOrganizationID(), 23 | getTestHelmRepositoryID(), 24 | ), 25 | Check: resource.ComposeAggregateTestCheckFunc( 26 | resource.TestCheckResourceAttr("data.qovery_helm_repository.test", "id", getTestHelmRepositoryID()), 27 | resource.TestCheckResourceAttr("data.qovery_helm_repository.test", "organization_id", getTestOrganizationID()), 28 | resource.TestCheckResourceAttr("data.qovery_helm_repository.test", "name", "Terraform Provider Tests"), 29 | resource.TestCheckResourceAttr("data.qovery_helm_repository.test", "kind", "OCI_DOCKER_HUB"), 30 | resource.TestCheckResourceAttr("data.qovery_helm_repository.test", "url", "oci://registry-1.docker.io"), 31 | resource.TestCheckResourceAttr("data.qovery_helm_repository.test", "description", "Helm Repository used for terraform tests."), 32 | resource.TestCheckResourceAttr("data.qovery_helm_repository.test", "skip_tls_verification", "false"), 33 | ), 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | func testAccHelmRepositoryDataSourceConfig(orgID string, helmID string) string { 40 | return fmt.Sprintf(` 41 | data "qovery_helm_repository" "test" { 42 | id = "%s" 43 | organization_id = "%s" 44 | } 45 | `, helmID, orgID, 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /qovery/data_source_annotations_group_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_AnnotationsGroupDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccAnnotationsGroupDataSourceConfig( 22 | getTestAnnotationsGroupID(), 23 | getTestOrganizationID(), 24 | ), 25 | Check: resource.ComposeAggregateTestCheckFunc( 26 | resource.TestCheckResourceAttr("data.qovery_annotations_group.test", "id", getTestAnnotationsGroupID()), 27 | resource.TestCheckResourceAttr("data.qovery_annotations_group.test", "organization_id", getTestOrganizationID()), 28 | resource.TestCheckResourceAttr("data.qovery_annotations_group.test", "name", "Terraform Provider Tests"), 29 | resource.TestCheckResourceAttr("data.qovery_annotations_group.test", "annotations.key1", "value1"), 30 | resource.TestCheckResourceAttr("data.qovery_annotations_group.test", "annotations.key2", "value2"), 31 | resource.TestCheckResourceAttr("data.qovery_annotations_group.test", "scopes.0", "DEPLOYMENTS"), 32 | resource.TestCheckResourceAttr("data.qovery_annotations_group.test", "scopes.1", "SERVICES"), 33 | ), 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | func testAccAnnotationsGroupDataSourceConfig(annotationsGroupID string, organizationID string) string { 40 | return fmt.Sprintf(` 41 | data "qovery_annotations_group" "test" { 42 | id = "%s" 43 | organization_id = "%s" 44 | } 45 | `, annotationsGroupID, organizationID, 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /docs/resources/scaleway_credentials.md: -------------------------------------------------------------------------------- 1 | # qovery_scaleway_credentials (Resource) 2 | 3 | Provides a Qovery SCALEWAY credentials resource. This can be used to create and manage Qovery SCALEWAY credentials. 4 | 5 | 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | ```terraform 13 | resource "qovery_scaleway_credentials" "my_scaleway_creds" { 14 | # Required 15 | organization_id = qovery_organization.my_organization.id 16 | name = "my_scaleway_creds" 17 | scaleway_access_key = "" 18 | scaleway_secret_key = "" 19 | scaleway_project_id = "" 20 | 21 | depends_on = [ 22 | qovery_organization.my_organization 23 | ] 24 | } 25 | ``` 26 | 27 | 28 | ## Schema 29 | 30 | ### Required 31 | 32 | - `name` (String) Name of the scaleway credentials. 33 | - `organization_id` (String) Id of the organization. 34 | - `scaleway_access_key` (String) Your SCALEWAY access key id. 35 | - `scaleway_organization_id` (String) Your SCALEWAY organization ID. 36 | - `scaleway_project_id` (String) Your SCALEWAY project ID. 37 | - `scaleway_secret_key` (String, Sensitive) Your SCALEWAY secret key. 38 | 39 | ### Read-Only 40 | 41 | - `id` (String) Id of the SCALEWAY credentials. 42 | ## Import 43 | ```shell 44 | terraform import qovery_scaleway_credentials.my_scaleway_creds "," 45 | ``` -------------------------------------------------------------------------------- /internal/domain/deploymentstage/deployment_stage_service.go: -------------------------------------------------------------------------------- 1 | package deploymentstage 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | ErrFailedToCreateDeploymentStage = errors.New("failed to create deployment stage") 11 | ErrFailedToGetDeploymentStage = errors.New("failed to get deployment stage") 12 | ErrFailedToUpdateDeploymentStage = errors.New("failed to update deployment stage") 13 | ErrFailedToDeleteDeploymentStage = errors.New("failed to delete deployment stage") 14 | ) 15 | 16 | // Service represents the interface to implement to handle the domain logic of a DeploymentStage 17 | type Service interface { 18 | Create(ctx context.Context, environmentID string, request UpsertServiceRequest) (*DeploymentStage, error) 19 | Get(ctx context.Context, environmentID string, deploymentStageID string) (*DeploymentStage, error) 20 | GetAllByEnvironmentID(ctx context.Context, environmentID string, deploymentStageName string) (*DeploymentStage, error) 21 | Update(ctx context.Context, deploymentStageID string, request UpsertServiceRequest) (*DeploymentStage, error) 22 | Delete(ctx context.Context, deploymentStageID string) error 23 | } 24 | 25 | // UpsertServiceRequest represents the parameters needed to create & update a DeploymentEnvironment Stage 26 | type UpsertServiceRequest struct { 27 | DeploymentStageUpsertRequest UpsertRepositoryRequest 28 | } 29 | 30 | // Validate returns an error to tell whether the UpsertServiceRequest is valid or not. 31 | func (r UpsertServiceRequest) Validate() error { 32 | if err := r.DeploymentStageUpsertRequest.Validate(); err != nil { 33 | return errors.Wrap(err, ErrInvalidUpsertRequest.Error()) 34 | } 35 | 36 | return nil 37 | } 38 | 39 | // IsValid returns a bool to tell whether the UpsertServiceRequest is valid or not. 40 | func (r UpsertServiceRequest) IsValid() bool { 41 | return r.Validate() == nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/domain/helm/helm_source.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | type SourceGitRepository struct { 4 | Url string 5 | Branch *string 6 | RootPath string 7 | GitTokenId *string 8 | } 9 | 10 | type SourceHelmRepository struct { 11 | RepositoryId string 12 | ChartName string 13 | ChartVersion string 14 | } 15 | 16 | type Source struct { 17 | GitRepository *SourceGitRepository 18 | HelmRepository *SourceHelmRepository 19 | } 20 | 21 | type NewHelmSourceGitRepository struct { 22 | Url string 23 | Branch *string 24 | RootPath string 25 | GitTokenId *string 26 | } 27 | 28 | type NewHelmSourceHelmRepository struct { 29 | RepositoryId string 30 | ChartName string 31 | ChartVersion string 32 | } 33 | 34 | type NewHelmSourceParams struct { 35 | HelmSourceGitRepository *NewHelmSourceGitRepository 36 | HelmSourceHelmRepository *NewHelmSourceHelmRepository 37 | } 38 | 39 | func NewHelmSource(params NewHelmSourceParams) (*Source, error) { 40 | var gitRepository *SourceGitRepository = nil 41 | if params.HelmSourceGitRepository != nil { 42 | gitRepository = &SourceGitRepository{ 43 | Url: params.HelmSourceGitRepository.Url, 44 | Branch: params.HelmSourceGitRepository.Branch, 45 | RootPath: params.HelmSourceGitRepository.RootPath, 46 | GitTokenId: params.HelmSourceGitRepository.GitTokenId, 47 | } 48 | } 49 | 50 | var helmRepository *SourceHelmRepository = nil 51 | if params.HelmSourceHelmRepository != nil { 52 | helmRepository = &SourceHelmRepository{ 53 | RepositoryId: params.HelmSourceHelmRepository.RepositoryId, 54 | ChartName: params.HelmSourceHelmRepository.ChartName, 55 | ChartVersion: params.HelmSourceHelmRepository.ChartVersion, 56 | } 57 | } 58 | 59 | newValuesOverride := &Source{ 60 | GitRepository: gitRepository, 61 | HelmRepository: helmRepository, 62 | } 63 | 64 | return newValuesOverride, nil 65 | } 66 | -------------------------------------------------------------------------------- /internal/domain/port/test_helper/test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "github.com/pkg/errors" 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/port" 7 | ) 8 | 9 | var ( 10 | DefaultPortName = "port-" + uuid.New().String() 11 | DefaultPortInternalPort int32 = 8080 12 | DefaultPortInvalidInternalPort int32 = -1 13 | DefaultPortExternalPort int32 = 80 14 | DefaultPortPubliclyAccessible = true 15 | DefaultPortProtocol = port.ProtocolHTTP 16 | 17 | DefaultValidPort = port.Port{ 18 | ID: uuid.New(), 19 | InternalPort: DefaultPortInternalPort, 20 | ExternalPort: &DefaultPortExternalPort, 21 | PubliclyAccessible: DefaultPortPubliclyAccessible, 22 | Protocol: &DefaultPortProtocol, 23 | } 24 | 25 | DefaultValidPortParams = port.NewPortParams{ 26 | PortID: uuid.New().String(), 27 | InternalPort: DefaultPortInternalPort, 28 | ExternalPort: &DefaultPortExternalPort, 29 | PubliclyAccessible: DefaultPortPubliclyAccessible, 30 | Protocol: DefaultPortProtocol.String(), 31 | } 32 | 33 | DefaultInvalidPort = port.Port{ 34 | ID: uuid.New(), 35 | InternalPort: DefaultPortInvalidInternalPort, 36 | ExternalPort: &DefaultPortExternalPort, 37 | PubliclyAccessible: DefaultPortPubliclyAccessible, 38 | Protocol: &DefaultPortProtocol, 39 | } 40 | 41 | DefaultInvalidPortParams = port.NewPortParams{ 42 | PortID: uuid.New().String(), 43 | InternalPort: DefaultPortInvalidInternalPort, 44 | ExternalPort: &DefaultPortExternalPort, 45 | PubliclyAccessible: DefaultPortPubliclyAccessible, 46 | Protocol: DefaultPortProtocol.String(), 47 | } 48 | 49 | DefaultInvalidPortParamsError = errors.New("invalid internal port param") 50 | ) 51 | -------------------------------------------------------------------------------- /client/apierrors/api_error.go: -------------------------------------------------------------------------------- 1 | package apierrors 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | type APIError struct { 10 | err error 11 | action APIAction 12 | resource APIResource 13 | resourceID string 14 | res *http.Response 15 | bufferedBody []byte 16 | } 17 | 18 | func IsNotFound(e *APIError) bool { 19 | if e == nil || e.res == nil { 20 | return false 21 | } 22 | 23 | // NOTE: consider 403 Forbidden as a 404 NotFound until the api is fixed 24 | return e.res.StatusCode == http.StatusNotFound || 25 | e.res.StatusCode == http.StatusForbidden 26 | } 27 | 28 | func IsBadRequest(e *APIError) bool { 29 | if e == nil || e.res == nil { 30 | return false 31 | } 32 | 33 | return e.res.StatusCode == http.StatusBadRequest 34 | } 35 | 36 | type errorPayload struct { 37 | Status int `json:"status"` 38 | Message string `json:"detail"` 39 | } 40 | 41 | func (e APIError) Error() string { 42 | return e.Detail() 43 | } 44 | 45 | func (e APIError) Summary() string { 46 | return fmt.Sprintf("Error on %s %s", e.resource, e.action) 47 | } 48 | 49 | func (e APIError) Detail() string { 50 | var extra string 51 | payload := e.errorPayload() 52 | 53 | if e.err != nil { 54 | extra = fmt.Sprintf("unexpected error: %s", e.err) 55 | if payload != nil && payload.Message != "" { 56 | extra = fmt.Sprintf("unexpected error: %s - %s", e.err, payload.Message) 57 | } 58 | } else { 59 | extra = fmt.Sprintf("unexpected status code: %d", e.res.StatusCode) 60 | } 61 | return fmt.Sprintf("Could not %s %s '%s', %s", e.action, e.resource, e.resourceID, extra) 62 | } 63 | 64 | func (e APIError) errorPayload() *errorPayload { 65 | if e.err == nil || len(e.bufferedBody) == 0 { 66 | return nil 67 | } 68 | 69 | var payload errorPayload 70 | if err := json.Unmarshal(e.bufferedBody, &payload); err != nil { 71 | return nil 72 | } 73 | 74 | return &payload 75 | } 76 | -------------------------------------------------------------------------------- /internal/infrastructure/repositories/qoveryapi/labels_group_qoveryapi_model.go: -------------------------------------------------------------------------------- 1 | package qoveryapi 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "github.com/pkg/errors" 6 | "github.com/qovery/qovery-client-go" 7 | "github.com/qovery/terraform-provider-qovery/internal/domain/labels_group" 8 | "github.com/qovery/terraform-provider-qovery/internal/domain/variable" 9 | ) 10 | 11 | func newDomainLabelsGroupFromQovery(labelsGroupResponse *qovery.OrganizationLabelsGroupResponse) (*labels_group.LabelsGroup, error) { 12 | if labelsGroupResponse == nil { 13 | return nil, variable.ErrNilVariable 14 | } 15 | 16 | labelsGroupID, err := uuid.Parse(labelsGroupResponse.Id) 17 | if err != nil { 18 | return nil, errors.Wrap(err, labels_group.ErrInvalidLabelsGroupIdParam.Error()) 19 | } 20 | 21 | return &labels_group.LabelsGroup{ 22 | Id: labelsGroupID, 23 | Name: labelsGroupResponse.Name, 24 | Labels: labelsGroupResponse.Labels, 25 | }, nil 26 | } 27 | 28 | func newQoveryLabelsGroupRequestFromDomain(request labels_group.UpsertRequest) *qovery.OrganizationLabelsGroupCreateRequest { 29 | labels := make([]qovery.Label, 0, len(request.Labels)) 30 | for _, label := range request.Labels { 31 | labels = append(labels, qovery.Label{Key: label.Key, Value: label.Value, PropagateToCloudProvider: label.PropagateToCloudProvider}) 32 | } 33 | 34 | return &qovery.OrganizationLabelsGroupCreateRequest{ 35 | Name: request.Name, 36 | Labels: labels, 37 | } 38 | } 39 | 40 | func NewQoveryServiceLabelsGroupRequestFromDomain(labelsGroupIds []string) ([]qovery.ServiceLabelRequest, error) { 41 | serviceLabelRequest := make([]qovery.ServiceLabelRequest, 0, len(labelsGroupIds)) 42 | for _, id := range labelsGroupIds { 43 | newLabelsRequest := qovery.ServiceLabelRequest{ 44 | Id: id, 45 | } 46 | 47 | serviceLabelRequest = append(serviceLabelRequest, newLabelsRequest) 48 | } 49 | 50 | return serviceLabelRequest, nil 51 | } 52 | -------------------------------------------------------------------------------- /client/cluster_status.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | 8 | "github.com/qovery/terraform-provider-qovery/client/apierrors" 9 | ) 10 | 11 | func (c *Client) getClusterStatus(ctx context.Context, organizationID string, clusterID string) (*qovery.ClusterStatus, *apierrors.APIError) { 12 | status, res, err := c.api.ClustersAPI. 13 | GetClusterStatus(ctx, organizationID, clusterID). 14 | Execute() 15 | if err != nil || res.StatusCode >= 400 { 16 | return nil, apierrors.NewReadError(apierrors.APIResourceClusterStatus, clusterID, res, err) 17 | } 18 | return status, nil 19 | } 20 | 21 | func (c *Client) updateClusterStatus(ctx context.Context, organizationID string, cluster *qovery.Cluster, desiredState qovery.ClusterStateEnum, forceUpdate bool) (*qovery.ClusterStateEnum, *apierrors.APIError) { 22 | // wait until we can stop the cluster - otherwise it will fail 23 | checker := newClusterFinalStateCheckerWaitFunc(c, organizationID, cluster.Id) 24 | if apiErr := wait(ctx, checker, nil); apiErr != nil { 25 | return nil, apiErr 26 | } 27 | 28 | // Wrap status call with retry logic to handle transient errors (DNS failures, timeouts, etc.) 29 | var status *qovery.ClusterStatus 30 | apiErr := retryAPICall(ctx, func(ctx context.Context) *apierrors.APIError { 31 | var err *apierrors.APIError 32 | status, err = c.getClusterStatus(ctx, organizationID, cluster.Id) 33 | return err 34 | }) 35 | if apiErr != nil { 36 | return nil, apiErr 37 | } 38 | 39 | if status.GetStatus() != desiredState || (status.GetStatus() == qovery.CLUSTERSTATEENUM_DEPLOYED && forceUpdate == true) { 40 | switch desiredState { 41 | case qovery.CLUSTERSTATEENUM_DEPLOYED: 42 | return c.deployCluster(ctx, organizationID, cluster) 43 | case qovery.CLUSTERSTATEENUM_STOPPED: 44 | return c.stopCluster(ctx, organizationID, cluster) 45 | } 46 | } 47 | 48 | return status.Status, nil 49 | } 50 | -------------------------------------------------------------------------------- /client/application_secrets.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | 8 | "github.com/qovery/terraform-provider-qovery/client/apierrors" 9 | ) 10 | 11 | func (c *Client) getApplicationSecrets(ctx context.Context, environmentID string) ([]*qovery.Secret, *apierrors.APIError) { 12 | vars, res, err := c.api.ApplicationSecretAPI. 13 | ListApplicationSecrets(ctx, environmentID). 14 | Execute() 15 | if err != nil || res.StatusCode >= 400 { 16 | return nil, apierrors.NewReadError(apierrors.APIResourceApplicationSecret, environmentID, res, err) 17 | } 18 | return secretResponseListToArray(vars, qovery.APIVARIABLESCOPEENUM_APPLICATION), nil 19 | } 20 | 21 | func (c *Client) updateApplicationSecrets(ctx context.Context, environmentID string, request SecretsDiff) *apierrors.APIError { 22 | for _, variable := range request.Delete { 23 | res, err := c.api.ApplicationSecretAPI. 24 | DeleteApplicationSecret(ctx, environmentID, variable.Id). 25 | Execute() 26 | if err != nil || res.StatusCode >= 400 { 27 | return apierrors.NewDeleteError(apierrors.APIResourceApplicationSecret, variable.Id, res, err) 28 | } 29 | } 30 | 31 | for _, variable := range request.Update { 32 | _, res, err := c.api.ApplicationSecretAPI. 33 | EditApplicationSecret(ctx, environmentID, variable.Id). 34 | SecretEditRequest(variable.SecretEditRequest). 35 | Execute() 36 | if err != nil || res.StatusCode >= 400 { 37 | return apierrors.NewUpdateError(apierrors.APIResourceApplicationSecret, variable.Id, res, err) 38 | } 39 | } 40 | 41 | for _, variable := range request.Create { 42 | _, res, err := c.api.ApplicationSecretAPI. 43 | CreateApplicationSecret(ctx, environmentID). 44 | SecretRequest(variable.SecretRequest). 45 | Execute() 46 | if err != nil || res.StatusCode >= 400 { 47 | return apierrors.NewCreateError(apierrors.APIResourceApplicationSecret, variable.Key, res, err) 48 | } 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /internal/domain/status/service_deployment_status.go: -------------------------------------------------------------------------------- 1 | package status 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | // ServiceDeploymentStatus is an enum that contains all the valid values of a status protocol. 10 | type ServiceDeploymentStatus string 11 | 12 | const ( 13 | ServiceDeploymentStatusNeverDeployed ServiceDeploymentStatus = "NEVER_DEPLOYED" 14 | ServiceDeploymentStatusOutOfDate ServiceDeploymentStatus = "OUT_OF_DATE" 15 | ServiceDeploymentStatusUpToDate ServiceDeploymentStatus = "UP_TO_DATE" 16 | ) 17 | 18 | // AllowedServiceDeploymentStatusValues contains all the valid values of a ServiceDeploymentStatus. 19 | var AllowedServiceDeploymentStatusValues = []ServiceDeploymentStatus{ 20 | ServiceDeploymentStatusNeverDeployed, 21 | ServiceDeploymentStatusOutOfDate, 22 | ServiceDeploymentStatusUpToDate, 23 | } 24 | 25 | // String returns the string value of a ServiceDeploymentStatus. 26 | func (v ServiceDeploymentStatus) String() string { 27 | return string(v) 28 | } 29 | 30 | // Validate returns an error to tell whether the ServiceDeploymentStatus is valid or not. 31 | func (v ServiceDeploymentStatus) Validate() error { 32 | if slices.Contains(AllowedServiceDeploymentStatusValues, v) { 33 | return nil 34 | } 35 | 36 | return fmt.Errorf("invalid value '%v' for ServiceDeploymentStatus: valid values are %v", v, AllowedServiceDeploymentStatusValues) 37 | } 38 | 39 | // IsValid returns a bool to tell whether the ServiceDeploymentStatus is valid or not. 40 | func (v ServiceDeploymentStatus) IsValid() bool { 41 | return v.Validate() == nil 42 | } 43 | 44 | // NewServiceDeploymentStatusFromString tries to turn a string into a ServiceDeploymentStatus. 45 | // It returns an error if the string is not a valid value. 46 | func NewServiceDeploymentStatusFromString(v string) (*ServiceDeploymentStatus, error) { 47 | ev := ServiceDeploymentStatus(v) 48 | 49 | if err := ev.Validate(); err != nil { 50 | return nil, err 51 | } 52 | 53 | return &ev, nil 54 | } 55 | -------------------------------------------------------------------------------- /internal/domain/organization/organization_plan.go: -------------------------------------------------------------------------------- 1 | package organization 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | // Plan is an enum that contains all the valid values of an organization plan. 10 | type Plan string 11 | 12 | const ( 13 | PlanFree Plan = "FREE" 14 | PlanTeam Plan = "TEAM" 15 | PlanTeamYearly Plan = "TEAM_YEARLY" 16 | PlanEnterprise Plan = "ENTERPRISE" 17 | PlanEnterpriseYearly Plan = "ENTERPRISE_YEARLY" 18 | PlanProfessional Plan = "PROFESSIONAL" 19 | PlanBusiness Plan = "BUSINESS" 20 | PlanUser2025 Plan = "USER_2025" 21 | PlanTeam2025 Plan = "TEAM_2025" 22 | PlanBusiness2025 Plan = "BUSINESS_2025" 23 | PlanEnterprise2025 Plan = "ENTERPRISE_2025" 24 | ) 25 | 26 | // AllowedPlanValues contains all the valid values of a Plan. 27 | var AllowedPlanValues = []Plan{ 28 | PlanFree, 29 | PlanTeam, 30 | PlanTeamYearly, 31 | PlanEnterprise, 32 | PlanEnterpriseYearly, 33 | PlanProfessional, 34 | PlanBusiness, 35 | PlanUser2025, 36 | PlanTeam2025, 37 | PlanBusiness2025, 38 | PlanEnterprise2025, 39 | } 40 | 41 | // String returns the string value of a Plan. 42 | func (v Plan) String() string { 43 | return string(v) 44 | } 45 | 46 | // Validate returns an error to tell whether the Plan is valid or not. 47 | func (v Plan) Validate() error { 48 | if slices.Contains(AllowedPlanValues, v) { 49 | return nil 50 | } 51 | 52 | return fmt.Errorf("invalid value '%v' for Plan: valid values are %v", v, AllowedPlanValues) 53 | } 54 | 55 | // IsValid returns a bool to tell whether the Plan is valid or not. 56 | func (v Plan) IsValid() bool { 57 | return v.Validate() == nil 58 | } 59 | 60 | // NewPlanFromString tries to turn a string into a Plan. 61 | // It returns an error if the string is not a valid value. 62 | func NewPlanFromString(v string) (*Plan, error) { 63 | ev := Plan(v) 64 | 65 | if err := ev.Validate(); err != nil { 66 | return nil, err 67 | } 68 | 69 | return &ev, nil 70 | } 71 | -------------------------------------------------------------------------------- /qovery/validators/int64_min_validator.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hashicorp/terraform-plugin-framework/schema/validator" 8 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | ) 11 | 12 | type Int64MinValidator struct { 13 | Min int64 14 | } 15 | 16 | // Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact. 17 | func (v Int64MinValidator) Description(ctx context.Context) string { 18 | return fmt.Sprintf("number value must be greater than %d", v.Min) 19 | } 20 | 21 | // MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact. 22 | func (v Int64MinValidator) MarkdownDescription(ctx context.Context) string { 23 | return fmt.Sprintf("number value must be greater than `%d`", v.Min) 24 | } 25 | 26 | // Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics. 27 | func (v Int64MinValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { 28 | // types.Int64 must be the attr.Value produced by the attr.Type in the schema for this attribute 29 | // for generic validators, use 30 | // https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ConvertValue 31 | // to convert into a known type. 32 | var number types.Int64 33 | resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.ConfigValue, &number)...) 34 | if resp.Diagnostics.HasError() { 35 | return 36 | } 37 | 38 | if number.IsUnknown() || number.IsNull() { 39 | return 40 | } 41 | 42 | if number.ValueInt64() < v.Min { 43 | resp.Diagnostics.AddAttributeError( 44 | req.Path, 45 | "Invalid Number Value", 46 | fmt.Sprintf("Number value must be greater than %d, got: %d.", v.Min, number.ValueInt64()), 47 | ) 48 | return 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /internal/domain/credentials/credentials_aws.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | var ( 9 | ErrInvalidUpsertAwsRequest = errors.New("invalid credentials upsert aws request") 10 | ) 11 | 12 | type AwsStaticCredentials struct { 13 | AccessKeyID string `validate:"required"` 14 | SecretAccessKey string `validate:"required"` 15 | } 16 | type AwsRoleCredentials struct { 17 | RoleArn string `validate:"required"` 18 | } 19 | 20 | // UpsertAwsRequest represents the parameters needed to create & update AWS Credentials. 21 | type UpsertAwsRequest struct { 22 | Name string `validate:"required"` 23 | StaticCredentials *AwsStaticCredentials 24 | RoleCredentials *AwsRoleCredentials 25 | } 26 | 27 | // Validate returns an error to tell whether the UpsertAwsRequest is valid or not. 28 | func (r UpsertAwsRequest) Validate() error { 29 | if err := validator.New().Struct(r); err != nil { 30 | return errors.Wrap(err, ErrInvalidUpsertAwsRequest.Error()) 31 | } 32 | 33 | // Validate that at least one credential type is provided 34 | if r.StaticCredentials == nil && r.RoleCredentials == nil { 35 | return errors.Wrap(errors.New("either StaticCredentials or RoleCredentials must be provided"), ErrInvalidUpsertAwsRequest.Error()) 36 | } 37 | 38 | // Validate StaticCredentials if provided 39 | if r.StaticCredentials != nil { 40 | if err := validator.New().Struct(r.StaticCredentials); err != nil { 41 | return errors.Wrap(err, ErrInvalidUpsertAwsRequest.Error()) 42 | } 43 | } 44 | 45 | // Validate RoleCredentials if provided 46 | if r.RoleCredentials != nil { 47 | if err := validator.New().Struct(r.RoleCredentials); err != nil { 48 | return errors.Wrap(err, ErrInvalidUpsertAwsRequest.Error()) 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | 55 | // IsValid returns a bool to tell whether the UpsertAwsRequest is valid or not. 56 | func (r UpsertAwsRequest) IsValid() bool { 57 | return r.Validate() == nil 58 | } 59 | -------------------------------------------------------------------------------- /qovery/data_source_project_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_ProjectDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccProjectDataSourceConfig( 22 | getTestProjectID(), 23 | ), 24 | Check: resource.ComposeAggregateTestCheckFunc( 25 | resource.TestCheckResourceAttr("data.qovery_project.test", "id", getTestProjectID()), 26 | resource.TestCheckResourceAttr("data.qovery_project.test", "organization_id", getTestOrganizationID()), 27 | resource.TestCheckResourceAttr("data.qovery_project.test", "name", "Terraform Provider Tests"), 28 | resource.TestCheckResourceAttr("data.qovery_project.test", "description", "Project used to run test for our terraform provider"), 29 | resource.TestCheckTypeSetElemNestedAttrs("data.qovery_project.test", "environment_variables.*", map[string]string{ 30 | "key": "MY_TERRAFORM_PROJECT_VARIABLE", 31 | "value": "MY_TERRAFORM_PROJECT_VARIABLE_VALUE", 32 | }), 33 | resource.TestCheckTypeSetElemNestedAttrs("data.qovery_project.test", "built_in_environment_variables.*", map[string]string{ 34 | "key": "QOVERY_PROJECT_ID", 35 | "value": "||Q_PRJ_ID||", 36 | }), 37 | resource.TestCheckTypeSetElemNestedAttrs("data.qovery_project.test", "secrets.*", map[string]string{ 38 | "key": "MY_TERRAFORM_PROJECT_SECRET", 39 | }), 40 | ), 41 | }, 42 | }, 43 | }) 44 | } 45 | 46 | func testAccProjectDataSourceConfig(projectID string) string { 47 | return fmt.Sprintf(` 48 | data "qovery_project" "test" { 49 | id = "%s" 50 | } 51 | `, projectID, 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /internal/domain/registry/registry_kind.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/slices" 7 | ) 8 | 9 | // Kind is an enum that contains all the valid values of a registry kind. 10 | type Kind string 11 | 12 | const ( 13 | KindECR Kind = "ECR" 14 | KindDocker Kind = "DOCR" 15 | KindScalewayCR Kind = "SCALEWAY_CR" 16 | KindDockerHub Kind = "DOCKER_HUB" 17 | KindGithubCr Kind = "GITHUB_CR" 18 | KindGithubEnterpriseCr Kind = "GITHUB_ENTERPRISE_CR" 19 | KindGitlabCr Kind = "GITLAB_CR" 20 | KindPublicECR Kind = "PUBLIC_ECR" 21 | KindGenericCR Kind = "GENERIC_CR" 22 | KindGcpArtifactRegistry Kind = "GCP_ARTIFACT_REGISTRY" 23 | KindAzureCr Kind = "AZURE_CR" 24 | ) 25 | 26 | // AllowedKindValues contains all the valid values of a Kind. 27 | var AllowedKindValues = []Kind{ 28 | KindECR, 29 | KindDocker, 30 | KindScalewayCR, 31 | KindDockerHub, 32 | KindGithubCr, 33 | KindGithubEnterpriseCr, 34 | KindGitlabCr, 35 | KindPublicECR, 36 | KindGenericCR, 37 | KindGcpArtifactRegistry, 38 | KindAzureCr, 39 | } 40 | 41 | // String returns the string value of a Kind. 42 | func (v Kind) String() string { 43 | return string(v) 44 | } 45 | 46 | // Validate returns an error to tell whether the Kind is valid or not. 47 | func (v Kind) Validate() error { 48 | if slices.Contains(AllowedKindValues, v) { 49 | return nil 50 | } 51 | 52 | return fmt.Errorf("invalid value '%v' for Kind: valid values are %v", v, AllowedKindValues) 53 | } 54 | 55 | // IsValid returns a bool to tell whether the Kind is valid or not. 56 | func (v Kind) IsValid() bool { 57 | return v.Validate() == nil 58 | } 59 | 60 | // NewKindFromString tries to turn a string into a Kind. 61 | // It returns an error if the string is not a valid value. 62 | func NewKindFromString(v string) (*Kind, error) { 63 | ev := Kind(v) 64 | 65 | if err := ev.Validate(); err != nil { 66 | return nil, err 67 | } 68 | 69 | return &ev, nil 70 | } 71 | -------------------------------------------------------------------------------- /internal/domain/job/test_helper/job_test_helper.go: -------------------------------------------------------------------------------- 1 | package test_helper 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | "github.com/qovery/terraform-provider-qovery/internal/domain/port" 6 | port_helper "github.com/qovery/terraform-provider-qovery/internal/domain/port/test_helper" 7 | "github.com/qovery/terraform-provider-qovery/internal/domain/secret" 8 | secrets_helper "github.com/qovery/terraform-provider-qovery/internal/domain/secret/test_helper" 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/variable" 10 | variables_helper "github.com/qovery/terraform-provider-qovery/internal/domain/variable/test_helper" 11 | ) 12 | 13 | var ( 14 | DefaultJobID = uuid.New() 15 | DefaultJobEnvironmentID = uuid.New() 16 | DefaultJobName = "MyJobName-" + uuid.New().String() 17 | DefaultInvalidJobName = "" 18 | DefaultJobCPU int32 = 100 19 | DefaultInvalidJobCPU int32 = 8 20 | DefaultJobMemory int32 = 250 21 | DefaultInvalidJobMemory int32 = 0 22 | DefaultJobMaxNbRestart uint32 = 0 23 | DefaultJobMaxDurationSeconds uint32 = 300 24 | DefaultJobAutoPreview = false 25 | DefaultJobPort *port.Port = &port_helper.DefaultValidPort 26 | DefaultInvalidJobPort *port.Port = &port_helper.DefaultInvalidPort 27 | DefaultJobEnvironmentVariables = []variable.Variable{variables_helper.DefaultValidVariable} 28 | DefaultInvalidJobEnvironmentVariables = []variable.Variable{variables_helper.DefaultInvalidVariable} 29 | DefaultJobEnvironmentSecrets = []secret.Secret{secrets_helper.DefaultValidSecret} 30 | DefaultInvalidJobEnvironmentSecrets = []secret.Secret{secrets_helper.DefaultInvalidSecret} 31 | DefaultJobDeploymentStageID = uuid.New() 32 | ) 33 | -------------------------------------------------------------------------------- /qovery/data_source_labels_group_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAcc_LabelsGroupDataSource(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 18 | Steps: []resource.TestStep{ 19 | // Read testing 20 | { 21 | Config: testAccLabelsGroupDataSourceConfig( 22 | getTestLabelsGroupID(), 23 | getTestOrganizationID(), 24 | ), 25 | Check: resource.ComposeAggregateTestCheckFunc( 26 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "id", getTestLabelsGroupID()), 27 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "organization_id", getTestOrganizationID()), 28 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "name", "Terraform Provider Tests"), 29 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "labels.0.key", "key1"), 30 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "labels.0.value", "value1"), 31 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "labels.0.propagate_to_cloud_provider", "false"), 32 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "labels.1.key", "key2"), 33 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "labels.1.value", "value2"), 34 | resource.TestCheckResourceAttr("data.qovery_labels_group.test", "labels.1.propagate_to_cloud_provider", "true"), 35 | ), 36 | }, 37 | }, 38 | }) 39 | } 40 | 41 | func testAccLabelsGroupDataSourceConfig(labelsGroupID string, organizationID string) string { 42 | return fmt.Sprintf(` 43 | data "qovery_labels_group" "test" { 44 | id = "%s" 45 | organization_id = "%s" 46 | } 47 | `, labelsGroupID, organizationID, 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /internal/infrastructure/repositories/qoveryapi/helm_repository_qoveryapi_models.go: -------------------------------------------------------------------------------- 1 | package qoveryapi 2 | 3 | import ( 4 | "github.com/AlekSi/pointer" 5 | "github.com/qovery/qovery-client-go" 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/helmRepository" 7 | 8 | "github.com/qovery/terraform-provider-qovery/internal/domain/registry" 9 | ) 10 | 11 | func newDomainHelmRepositoryFromQovery(v *qovery.HelmRepositoryResponse, organizationID string) (*helmRepository.HelmRepository, error) { 12 | if v == nil { 13 | return nil, registry.ErrNilRegistry 14 | } 15 | 16 | return helmRepository.NewHelmRepository(helmRepository.NewHelmRepositoryParams{ 17 | RepositoryId: v.GetId(), 18 | OrganizationID: organizationID, 19 | Name: v.GetName(), 20 | Kind: string(v.GetKind()), 21 | URL: v.GetUrl(), 22 | Description: v.Description, 23 | SkiTlsVerification: v.SkipTlsVerification, 24 | }) 25 | } 26 | 27 | func newQoveryHelmRepositoryRequestFromDomain(request helmRepository.UpsertRequest) (*qovery.HelmRepositoryRequest, error) { 28 | kind, err := qovery.NewHelmRepositoryKindEnumFromValue(request.Kind) 29 | if err != nil { 30 | return nil, registry.ErrInvalidKindParam 31 | } 32 | 33 | return &qovery.HelmRepositoryRequest{ 34 | Name: request.Name, 35 | Kind: *kind, 36 | Url: pointer.ToString(request.URL), 37 | Description: request.Description, 38 | SkipTlsVerification: request.SkiTlsVerification, 39 | Config: qovery.HelmRepositoryRequestConfig{ 40 | AccessKeyId: request.Config.AccessKeyID, 41 | SecretAccessKey: request.Config.SecretAccessKey, 42 | Region: request.Config.Region, 43 | ScalewayAccessKey: request.Config.ScalewayAccessKey, 44 | ScalewaySecretKey: request.Config.ScalewaySecretKey, 45 | ScalewayProjectId: request.Config.ScalewayProjectId, 46 | Username: request.Config.Username, 47 | Password: request.Config.Password, 48 | }, 49 | }, nil 50 | } 51 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - env: 9 | # goreleaser does not work with CGO, it could also complicate 10 | # usage by users in CI/CD systems like Terraform Cloud where 11 | # they are unable to install libraries. 12 | - CGO_ENABLED=0 13 | mod_timestamp: '{{ .CommitTimestamp }}' 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - '-s -w -X main.version={{.Version}}' 18 | goos: 19 | - freebsd 20 | - windows 21 | - linux 22 | - darwin 23 | goarch: 24 | - amd64 25 | - '386' 26 | - arm 27 | - arm64 28 | ignore: 29 | - goos: darwin 30 | goarch: '386' 31 | binary: '{{ .ProjectName }}_v{{ .Version }}' 32 | archives: 33 | - format: zip 34 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 35 | checksum: 36 | extra_files: 37 | - glob: 'terraform-registry-manifest.json' 38 | name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 39 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 40 | algorithm: sha256 41 | signs: 42 | - artifacts: checksum 43 | args: 44 | # if you are using this in a GitHub action or some other automated pipeline, you 45 | # need to pass the batch flag to indicate its not interactive. 46 | - "--batch" 47 | - "--local-user" 48 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 49 | - "--output" 50 | - "${signature}" 51 | - "--detach-sign" 52 | - "${artifact}" 53 | release: 54 | extra_files: 55 | - glob: 'terraform-registry-manifest.json' 56 | name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 57 | # If you want to manually examine the release before its live, uncomment this line: 58 | # draft: true 59 | changelog: 60 | skip: true -------------------------------------------------------------------------------- /docs/resources/deployment_stage.md: -------------------------------------------------------------------------------- 1 | # qovery_deployment_stage (Resource) 2 | 3 | Provides a Qovery deployment stage resource. This can be used to create and manage Qovery deployment stages. 4 | 5 | 6 | ## Example 7 | 8 |
9 | If you're not familiar with Terraform or just want more examples, you can configure everything you need directly from the Qovery console. Then, use our Terraform exporter feature to generate the corresponding Terraform code. 10 |

11 | 12 | ```terraform 13 | resource "qovery_deployment_stage" "my_deployment_stage" { 14 | # Required 15 | environment_id = qovery_environment.my_environment.id 16 | name = "MyDeploymentStage" 17 | 18 | # Optional 19 | description = "" 20 | is_after = qovery_deployment_stage.first_deployment_stage.id 21 | is_before = qovery_deployment_stage.third_deployment_stage.id 22 | 23 | depends_on = [ 24 | qovery_environment.my_environment 25 | ] 26 | } 27 | ``` 28 | 29 | You can find complete examples within these repositories: 30 | * [Deploy services with a specific order](https://github.com/Qovery/terraform-examples/tree/main/examples/deploy-services-with-a-specific-order) 31 | 32 | ## Schema 33 | 34 | ### Required 35 | 36 | - `environment_id` (String) Id of the environment. 37 | - `name` (String) Name of the deployment stage. 38 | 39 | ### Optional 40 | 41 | - `description` (String) Description of the deployment stage. 42 | - `is_after` (String) Move the current deployment stage after the target deployment stage 43 | - `is_before` (String) Move the current deployment stage before the target deployment stage 44 | 45 | ### Read-Only 46 | 47 | - `id` (String) Id of the deployment stage. 48 | ## Import 49 | ```shell 50 | terraform import qovery_deployment_stage.my_deployment_stage "," 51 | ``` -------------------------------------------------------------------------------- /qovery/validators/int64_min_max_validator.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hashicorp/terraform-plugin-framework/schema/validator" 8 | "github.com/hashicorp/terraform-plugin-framework/tfsdk" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | ) 11 | 12 | type Int64MinMaxValidator struct { 13 | Min int64 14 | Max int64 15 | } 16 | 17 | // Description returns a plain text description of the validator's behavior, suitable for a practitioner to understand its impact. 18 | func (v Int64MinMaxValidator) Description(ctx context.Context) string { 19 | return fmt.Sprintf("number value must be greater than %d", v.Min) 20 | } 21 | 22 | // MarkdownDescription returns a markdown formatted description of the validator's behavior, suitable for a practitioner to understand its impact. 23 | func (v Int64MinMaxValidator) MarkdownDescription(ctx context.Context) string { 24 | return fmt.Sprintf("number value must be greater than `%d`", v.Min) 25 | } 26 | 27 | // Validate runs the main validation logic of the validator, reading configuration data out of `req` and updating `resp` with diagnostics. 28 | func (v Int64MinMaxValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { 29 | // types.Int64 must be the attr.Value produced by the attr.Type in the schema for this attribute 30 | // for generic validators, use 31 | // https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework/tfsdk#ConvertValue 32 | // to convert into a known type. 33 | var number types.Int64 34 | resp.Diagnostics.Append(tfsdk.ValueAs(ctx, req.ConfigValue, &number)...) 35 | if resp.Diagnostics.HasError() { 36 | return 37 | } 38 | 39 | if number.IsUnknown() || number.IsNull() { 40 | return 41 | } 42 | 43 | if number.ValueInt64() < v.Min || number.ValueInt64() > v.Max { 44 | resp.Diagnostics.AddAttributeError( 45 | req.Path, 46 | "Invalid Number Value", 47 | fmt.Sprintf("Number value must be between (inclusive) %d and %d, got: %d.", v.Min, v.Max, number.ValueInt64()), 48 | ) 49 | return 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/infrastructure/repositories/qoveryapi/container_registry_qoveryapi_models.go: -------------------------------------------------------------------------------- 1 | package qoveryapi 2 | 3 | import ( 4 | "github.com/AlekSi/pointer" 5 | "github.com/qovery/qovery-client-go" 6 | 7 | "github.com/qovery/terraform-provider-qovery/internal/domain/registry" 8 | ) 9 | 10 | // newDomainRegistryFromQovery takes a qovery.ContainerRegistryResponse returned by the API client and turns it into the domain model registry.Registry. 11 | func newDomainRegistryFromQovery(v *qovery.ContainerRegistryResponse, organizationID string) (*registry.Registry, error) { 12 | if v == nil { 13 | return nil, registry.ErrNilRegistry 14 | } 15 | 16 | return registry.NewRegistry(registry.NewRegistryParams{ 17 | RegistryID: v.GetId(), 18 | OrganizationID: organizationID, 19 | Name: v.GetName(), 20 | Kind: string(v.GetKind()), 21 | URL: v.GetUrl(), 22 | Description: v.Description, 23 | }) 24 | } 25 | 26 | // newQoveryContainerRegistryRequestFromDomain takes the domain request registry.UpsertRequest and turns it into a qovery.ContainerRegistryRequest to make the api call. 27 | func newQoveryContainerRegistryRequestFromDomain(request registry.UpsertRequest) (*qovery.ContainerRegistryRequest, error) { 28 | kind, err := qovery.NewContainerRegistryKindEnumFromValue(request.Kind) 29 | if err != nil { 30 | return nil, registry.ErrInvalidKindParam 31 | } 32 | 33 | return &qovery.ContainerRegistryRequest{ 34 | Name: request.Name, 35 | Kind: *kind, 36 | Url: pointer.ToString(request.URL), 37 | Description: request.Description, 38 | Config: qovery.ContainerRegistryRequestConfig{ 39 | AccessKeyId: request.Config.AccessKeyID, 40 | SecretAccessKey: request.Config.SecretAccessKey, 41 | Region: request.Config.Region, 42 | ScalewayAccessKey: request.Config.ScalewayAccessKey, 43 | ScalewaySecretKey: request.Config.ScalewaySecretKey, 44 | ScalewayProjectId: request.Config.ScalewayProjectId, 45 | Username: request.Config.Username, 46 | Password: request.Config.Password, 47 | }, 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /qovery/data_source_environment_test.go: -------------------------------------------------------------------------------- 1 | //go:build integration && !unit 2 | // +build integration,!unit 3 | 4 | package qovery_test 5 | 6 | import ( 7 | "fmt" 8 | "regexp" 9 | "testing" 10 | 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 12 | ) 13 | 14 | func TestAcc_EnvironmentDataSource(t *testing.T) { 15 | t.Parallel() 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, 19 | Steps: []resource.TestStep{ 20 | // Read testing 21 | { 22 | Config: testAccEnvironmentDataSourceConfig( 23 | getTestEnvironmentID(), 24 | ), 25 | Check: resource.ComposeAggregateTestCheckFunc( 26 | resource.TestCheckResourceAttr("data.qovery_environment.test", "id", getTestEnvironmentID()), 27 | resource.TestCheckResourceAttr("data.qovery_environment.test", "project_id", getTestProjectID()), 28 | resource.TestCheckResourceAttr("data.qovery_environment.test", "cluster_id", getTestClusterID()), 29 | resource.TestCheckResourceAttr("data.qovery_environment.test", "name", "tests"), 30 | resource.TestCheckResourceAttr("data.qovery_environment.test", "mode", "DEVELOPMENT"), 31 | resource.TestCheckTypeSetElemNestedAttrs("data.qovery_environment.test", "environment_variables.*", map[string]string{ 32 | "key": "MY_TERRAFORM_ENVIRONMENT_VARIABLE", 33 | "value": "MY_TERRAFORM_ENVIRONMENT_VARIABLE_VALUE", 34 | }), 35 | resource.TestMatchTypeSetElemNestedAttrs("data.qovery_environment.test", "built_in_environment_variables.*", map[string]*regexp.Regexp{ 36 | "key": regexp.MustCompile(`^QOVERY_`), 37 | }), 38 | resource.TestCheckTypeSetElemNestedAttrs("data.qovery_environment.test", "secrets.*", map[string]string{ 39 | "key": "MY_TERRAFORM_ENVIRONMENT_SECRET", 40 | }), 41 | ), 42 | }, 43 | }, 44 | }) 45 | } 46 | 47 | func testAccEnvironmentDataSourceConfig(environmentID string) string { 48 | return fmt.Sprintf(` 49 | data "qovery_environment" "test" { 50 | id = "%s" 51 | } 52 | `, environmentID, 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /internal/domain/helm/helm_repository.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | //go:generate mockery --testonly --with-expecter --name=Repository --structname=HelmRepository --filename=helm_repository_mock.go --output=../../infrastructure/repositories/mocks_test/ --outpkg=mocks_test 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/qovery/qovery-client-go" 9 | 10 | "github.com/qovery/terraform-provider-qovery/client" 11 | 12 | "github.com/go-playground/validator/v10" 13 | "github.com/pkg/errors" 14 | 15 | "github.com/qovery/terraform-provider-qovery/internal/domain/secret" 16 | "github.com/qovery/terraform-provider-qovery/internal/domain/variable" 17 | ) 18 | 19 | type Repository interface { 20 | Create(ctx context.Context, environmentID string, request UpsertRepositoryRequest) (*Helm, error) 21 | Get(ctx context.Context, helmID string, advancedSettingsJsonFromState string, isTriggeredFromImport bool) (*Helm, error) 22 | Update(ctx context.Context, helmID string, request UpsertRepositoryRequest) (*Helm, error) 23 | Delete(ctx context.Context, helmID string) error 24 | } 25 | 26 | type UpsertRepositoryRequest struct { 27 | Name string `validate:"required"` 28 | Description *string 29 | IconUri *string 30 | TimeoutSec *int32 31 | AutoPreview qovery.NullableBool 32 | AutoDeploy bool 33 | Arguments []string 34 | AllowClusterWideResources bool 35 | Source Source 36 | ValuesOverride ValuesOverride 37 | Ports *[]Port 38 | EnvironmentVariables []variable.UpsertRequest 39 | Secrets []secret.UpsertRequest 40 | DeploymentStageID string 41 | AdvancedSettingsJson string 42 | CustomDomains client.CustomDomainsDiff 43 | } 44 | 45 | func (r UpsertRepositoryRequest) Validate() error { 46 | if err := validator.New().Struct(r); err != nil { 47 | return errors.Wrap(err, ErrInvalidHelmUpsertRequest.Error()) 48 | } 49 | 50 | return nil 51 | } 52 | 53 | func (r UpsertRepositoryRequest) IsValid() bool { 54 | return r.Validate() == nil 55 | } 56 | -------------------------------------------------------------------------------- /internal/domain/newdeployment/newdeployment_test.go: -------------------------------------------------------------------------------- 1 | package newdeployment 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/uuid" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestShouldFailWhenCreatingNewIncoherentDeployment(t *testing.T) { 11 | t.Parallel() 12 | 13 | testCases := []struct { 14 | TestName string 15 | Params NewDeploymentParams 16 | ExpectedError error 17 | ExpectedDeployment *Deployment 18 | }{ 19 | { 20 | TestName: "should_fail_with_no_environment_id", 21 | Params: NewDeploymentParams{ 22 | DesiredState: "RUNNING", 23 | }, 24 | ExpectedError: ErrInvalidEnvironmentIdParam, 25 | ExpectedDeployment: nil, 26 | }, 27 | { 28 | TestName: "should_fail_with_wrong_desired_state", 29 | Params: NewDeploymentParams{ 30 | DesiredState: "WRONG_DESIRED_STATE", 31 | }, 32 | ExpectedError: ErrInvalidDeployment, 33 | ExpectedDeployment: nil, 34 | }, 35 | { 36 | TestName: "should_fail_with_wrong_environment_id", 37 | Params: NewDeploymentParams{ 38 | EnvironmentID: "WRONG_UUID", 39 | DesiredState: "RUNNING", 40 | }, 41 | ExpectedError: ErrInvalidEnvironmentIdParam, 42 | ExpectedDeployment: nil, 43 | }, 44 | } 45 | 46 | for _, tc := range testCases { 47 | t.Run(tc.TestName, func(t *testing.T) { 48 | deployment, err := NewDeployment(tc.Params) 49 | assert.NotNil(t, err) 50 | assert.ErrorContains(t, err, tc.ExpectedError.Error()) 51 | assert.Nil(t, deployment) 52 | }) 53 | } 54 | } 55 | 56 | func TestShouldCreateNewEnvironmentDeployment(t *testing.T) { 57 | t.Run("should_create_environment_deployment", func(t *testing.T) { 58 | params := NewDeploymentParams{ 59 | EnvironmentID: uuid.NewString(), 60 | DesiredState: "RUNNING", 61 | } 62 | 63 | deployment, err := NewDeployment(params) 64 | 65 | expectedEnvironmentID, _ := uuid.Parse(params.EnvironmentID) 66 | 67 | assert.NoError(t, err) 68 | assert.NotNil(t, deployment) 69 | assert.Equal(t, &expectedEnvironmentID, deployment.EnvironmentID) 70 | assert.Equal(t, RUNNING, deployment.DesiredState) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /docs/data-sources/database.md: -------------------------------------------------------------------------------- 1 | # qovery_database (Data Source) 2 | 3 | Provides a Qovery database resource. This can be used to create and manage Qovery databases. 4 | ## Example Usage 5 | ```terraform 6 | data "qovery_database" "my_database" { 7 | id = "" 8 | } 9 | ``` 10 | 11 | 12 | ## Schema 13 | 14 | ### Required 15 | 16 | - `id` (String) Id of the database. 17 | 18 | ### Optional 19 | 20 | - `accessibility` (String) Accessibility of the database. 21 | - Can be: `PRIVATE`, `PUBLIC`. 22 | - Default: `PUBLIC`. 23 | - `annotations_group_ids` (Set of String) List of annotations group ids 24 | - `cpu` (Number) CPU of the database in millicores (m) [1000m = 1 CPU]. 25 | - Must be: `>= 250`. 26 | - Default: `250`. 27 | - `deployment_stage_id` (String) Id of the deployment stage. 28 | - `icon_uri` (String) Icon URI representing the database. 29 | - `instance_type` (String) Instance type of the database. 30 | - `labels_group_ids` (Set of String) List of labels group ids 31 | - `memory` (Number) RAM of the database in MB [1024MB = 1GB]. 32 | - Must be: `>= 100`. 33 | - Default: `256`. 34 | - `storage` (Number) Storage of the database in GB [1024MB = 1GB] [NOTE: can't be updated after creation]. 35 | - Must be: `>= 10`. 36 | - Default: `10`. 37 | 38 | ### Read-Only 39 | 40 | - `environment_id` (String) Id of the environment. 41 | - `external_host` (String) The database external FQDN host [NOTE: only if your container is using a publicly accessible port]. 42 | - `internal_host` (String) The database internal host (Recommended for your application) 43 | - `login` (String) The login to connect to your database 44 | - `mode` (String) Mode of the database [NOTE: can't be updated after creation]. 45 | - Can be: `CONTAINER`, `MANAGED`. 46 | - `name` (String) Name of the database. 47 | - `password` (String) The password to connect to your database 48 | - `port` (Number) The port to connect to your database 49 | - `type` (String) Type of the database [NOTE: can't be updated after creation]. 50 | - Can be: `MONGODB`, `MYSQL`, `POSTGRESQL`, `REDIS`. 51 | - `version` (String) Version of the database 52 | 53 | -------------------------------------------------------------------------------- /client/application_custom_domains.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | 8 | "github.com/qovery/terraform-provider-qovery/client/apierrors" 9 | ) 10 | 11 | func (c *Client) getApplicationCustomDomains(ctx context.Context, applicationID string) ([]*qovery.CustomDomain, *apierrors.APIError) { 12 | applicationDomains, res, err := c.api.ApplicationCustomDomainAPI. 13 | ListApplicationCustomDomain(ctx, applicationID). 14 | Execute() 15 | if err != nil || res.StatusCode >= 400 { 16 | return nil, apierrors.NewReadError(apierrors.APIResourceApplicationCustomDomain, applicationID, res, err) 17 | } 18 | return customDomainResponseListToArray(applicationDomains), nil 19 | } 20 | 21 | func (c *Client) updateApplicationCustomDomains(ctx context.Context, applicationID string, request CustomDomainsDiff) *apierrors.APIError { 22 | for _, variable := range request.Delete { 23 | res, err := c.api.ApplicationCustomDomainAPI. 24 | DeleteCustomDomain(ctx, applicationID, variable.Id). 25 | Execute() 26 | if err != nil || res.StatusCode >= 400 { 27 | return apierrors.NewDeleteError(apierrors.APIResourceApplicationCustomDomain, variable.Id, res, err) 28 | } 29 | } 30 | 31 | for _, customDomainToUpdate := range request.Update { 32 | _, res, err := c.api.ApplicationCustomDomainAPI. 33 | EditCustomDomain(ctx, applicationID, customDomainToUpdate.Id). 34 | CustomDomainRequest(customDomainToUpdate.CustomDomainRequest). 35 | Execute() 36 | if err != nil || res.StatusCode >= 400 { 37 | return apierrors.NewUpdateError(apierrors.APIResourceApplicationCustomDomain, customDomainToUpdate.Id, res, err) 38 | } 39 | } 40 | 41 | for _, customDomainToCreate := range request.Create { 42 | _, res, err := c.api.ApplicationCustomDomainAPI. 43 | CreateApplicationCustomDomain(ctx, applicationID). 44 | CustomDomainRequest(customDomainToCreate.CustomDomainRequest). 45 | Execute() 46 | if err != nil || res.StatusCode >= 400 { 47 | return apierrors.NewCreateError(apierrors.APIResourceApplicationCustomDomain, customDomainToCreate.Domain, res, err) 48 | } 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /internal/domain/credentials/credentials_test.go: -------------------------------------------------------------------------------- 1 | package credentials_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brianvoe/gofakeit/v6" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/credentials" 10 | ) 11 | 12 | func TestNewCredentials(t *testing.T) { 13 | t.Parallel() 14 | 15 | testCases := []struct { 16 | TestName string 17 | Params credentials.NewCredentialsParams 18 | ExpectedError error 19 | }{ 20 | { 21 | TestName: "fail_with_invalid_credentials_id", 22 | Params: credentials.NewCredentialsParams{ 23 | OrganizationID: gofakeit.UUID(), 24 | Name: gofakeit.Name(), 25 | }, 26 | ExpectedError: credentials.ErrInvalidCredentialsID, 27 | }, 28 | { 29 | TestName: "fail_with_invalid_organization_id", 30 | Params: credentials.NewCredentialsParams{ 31 | CredentialsID: gofakeit.UUID(), 32 | Name: gofakeit.Name(), 33 | }, 34 | ExpectedError: credentials.ErrInvalidCredentialsOrganizationID, 35 | }, 36 | { 37 | TestName: "fail_with_invalid_name", 38 | Params: credentials.NewCredentialsParams{ 39 | CredentialsID: gofakeit.UUID(), 40 | OrganizationID: gofakeit.UUID(), 41 | }, 42 | ExpectedError: credentials.ErrInvalidCredentialsName, 43 | }, 44 | { 45 | TestName: "success", 46 | Params: credentials.NewCredentialsParams{ 47 | CredentialsID: gofakeit.UUID(), 48 | OrganizationID: gofakeit.UUID(), 49 | Name: gofakeit.Name(), 50 | }, 51 | }, 52 | } 53 | 54 | for _, tc := range testCases { 55 | tc := tc 56 | t.Run(tc.TestName, func(t *testing.T) { 57 | creds, err := credentials.NewCredentials(tc.Params) 58 | if tc.ExpectedError != nil { 59 | assert.ErrorContains(t, err, tc.ExpectedError.Error()) 60 | assert.Nil(t, creds) 61 | return 62 | } 63 | 64 | assert.NoError(t, err) 65 | assert.NotNil(t, creds) 66 | assert.Equal(t, tc.Params.CredentialsID, creds.ID.String()) 67 | assert.Equal(t, tc.Params.OrganizationID, creds.OrganizationID.String()) 68 | assert.Equal(t, tc.Params.Name, creds.Name) 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /internal/domain/organization/organization_test.go: -------------------------------------------------------------------------------- 1 | package organization_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brianvoe/gofakeit/v6" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/organization" 10 | ) 11 | 12 | func TestNewOrganization(t *testing.T) { 13 | t.Parallel() 14 | 15 | testCases := []struct { 16 | TestName string 17 | Params organization.NewOrganizationParams 18 | ExpectedError error 19 | }{ 20 | { 21 | TestName: "fail_with_invalid_organization_id", 22 | Params: organization.NewOrganizationParams{ 23 | Name: gofakeit.Name(), 24 | Plan: organization.PlanFree.String(), 25 | }, 26 | ExpectedError: organization.ErrInvalidOrganizationIDParam, 27 | }, 28 | { 29 | TestName: "fail_with_invalid_name", 30 | Params: organization.NewOrganizationParams{ 31 | OrganizationID: gofakeit.UUID(), 32 | Plan: organization.PlanFree.String(), 33 | }, 34 | ExpectedError: organization.ErrInvalidNameParam, 35 | }, 36 | { 37 | TestName: "fail_with_invalid_plan", 38 | Params: organization.NewOrganizationParams{ 39 | OrganizationID: gofakeit.UUID(), 40 | Name: gofakeit.Name(), 41 | }, 42 | ExpectedError: organization.ErrInvalidPlanParam, 43 | }, 44 | { 45 | TestName: "success", 46 | Params: organization.NewOrganizationParams{ 47 | OrganizationID: gofakeit.UUID(), 48 | Name: gofakeit.Name(), 49 | Plan: organization.PlanFree.String(), 50 | }, 51 | }, 52 | } 53 | 54 | for _, tc := range testCases { 55 | tc := tc 56 | t.Run(tc.TestName, func(t *testing.T) { 57 | orga, err := organization.NewOrganization(tc.Params) 58 | if tc.ExpectedError != nil { 59 | assert.ErrorContains(t, err, tc.ExpectedError.Error()) 60 | assert.Nil(t, orga) 61 | return 62 | } 63 | 64 | assert.NoError(t, err) 65 | assert.NotNil(t, orga) 66 | assert.Equal(t, tc.Params.OrganizationID, orga.ID.String()) 67 | assert.Equal(t, tc.Params.Name, orga.Name) 68 | assert.Equal(t, tc.Params.Plan, orga.Plan.String()) 69 | }) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /qovery/resource_aws_credentials_model.go: -------------------------------------------------------------------------------- 1 | package qovery 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/types" 5 | 6 | "github.com/qovery/terraform-provider-qovery/internal/domain/credentials" 7 | ) 8 | 9 | type AWSCredentials struct { 10 | Id types.String `tfsdk:"id"` 11 | OrganizationId types.String `tfsdk:"organization_id"` 12 | Name types.String `tfsdk:"name"` 13 | AccessKeyId types.String `tfsdk:"access_key_id"` 14 | SecretAccessKey types.String `tfsdk:"secret_access_key"` 15 | RoleArn types.String `tfsdk:"role_arn"` 16 | } 17 | 18 | type AWSCredentialsDataSource struct { 19 | Id types.String `tfsdk:"id"` 20 | OrganizationId types.String `tfsdk:"organization_id"` 21 | Name types.String `tfsdk:"name"` 22 | } 23 | 24 | func (creds AWSCredentials) toUpsertAwsRequest() credentials.UpsertAwsRequest { 25 | if creds.RoleArn.IsNull() { 26 | return credentials.UpsertAwsRequest{ 27 | Name: ToString(creds.Name), 28 | StaticCredentials: &credentials.AwsStaticCredentials{ 29 | AccessKeyID: ToString(creds.AccessKeyId), 30 | SecretAccessKey: ToString(creds.SecretAccessKey), 31 | }, 32 | } 33 | } 34 | return credentials.UpsertAwsRequest{ 35 | Name: ToString(creds.Name), 36 | RoleCredentials: &credentials.AwsRoleCredentials{ 37 | RoleArn: ToString(creds.RoleArn), 38 | }, 39 | } 40 | } 41 | 42 | func convertDomainCredentialsToAWSCredentials(creds *credentials.Credentials, plan AWSCredentials) AWSCredentials { 43 | return AWSCredentials{ 44 | Id: FromString(creds.ID.String()), 45 | OrganizationId: FromString(creds.OrganizationID.String()), 46 | Name: FromString(creds.Name), 47 | AccessKeyId: plan.AccessKeyId, 48 | SecretAccessKey: plan.SecretAccessKey, 49 | } 50 | } 51 | 52 | func convertDomainCredentialsToAWSCredentialsDataSource(creds *credentials.Credentials) AWSCredentialsDataSource { 53 | return AWSCredentialsDataSource{ 54 | Id: FromString(creds.ID.String()), 55 | OrganizationId: FromString(creds.OrganizationID.String()), 56 | Name: FromString(creds.Name), 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /internal/domain/secret/secret_test.go: -------------------------------------------------------------------------------- 1 | package secret_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/brianvoe/gofakeit/v6" 7 | "github.com/stretchr/testify/assert" 8 | 9 | "github.com/qovery/terraform-provider-qovery/internal/domain/secret" 10 | "github.com/qovery/terraform-provider-qovery/internal/domain/variable" 11 | ) 12 | 13 | func TestNewSecret(t *testing.T) { 14 | t.Parallel() 15 | 16 | testCases := []struct { 17 | TestName string 18 | Params secret.NewSecretParams 19 | ExpectedError error 20 | }{ 21 | { 22 | TestName: "fail_with_invalid_secret_id", 23 | Params: secret.NewSecretParams{ 24 | Scope: variable.ScopeApplication.String(), 25 | Key: gofakeit.Name(), 26 | Type: "VALUE", 27 | }, 28 | ExpectedError: secret.ErrInvalidSecretIDParam, 29 | }, 30 | { 31 | TestName: "fail_with_invalid_key", 32 | Params: secret.NewSecretParams{ 33 | SecretID: gofakeit.UUID(), 34 | Scope: variable.ScopeApplication.String(), 35 | Type: "VALUE", 36 | }, 37 | ExpectedError: secret.ErrInvalidKeyParam, 38 | }, 39 | { 40 | TestName: "fail_with_invalid_scope", 41 | Params: secret.NewSecretParams{ 42 | SecretID: gofakeit.UUID(), 43 | Key: gofakeit.Name(), 44 | Type: "VALUE", 45 | }, 46 | ExpectedError: secret.ErrInvalidScopeParam, 47 | }, 48 | { 49 | TestName: "success", 50 | Params: secret.NewSecretParams{ 51 | SecretID: gofakeit.UUID(), 52 | Scope: variable.ScopeApplication.String(), 53 | Key: gofakeit.Name(), 54 | Type: "VALUE", 55 | }, 56 | }, 57 | } 58 | 59 | for _, tc := range testCases { 60 | tc := tc 61 | t.Run(tc.TestName, func(t *testing.T) { 62 | v, err := secret.NewSecret(tc.Params) 63 | if tc.ExpectedError != nil { 64 | assert.ErrorContains(t, err, tc.ExpectedError.Error()) 65 | assert.Nil(t, v) 66 | return 67 | } 68 | 69 | assert.NoError(t, err) 70 | assert.NotNil(t, v) 71 | assert.True(t, v.IsValid()) 72 | assert.Equal(t, tc.Params.SecretID, v.ID.String()) 73 | assert.Equal(t, tc.Params.Key, v.Key) 74 | assert.Equal(t, tc.Params.Type, v.Type) 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /client/database_status.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/qovery/qovery-client-go" 7 | 8 | "github.com/qovery/terraform-provider-qovery/client/apierrors" 9 | ) 10 | 11 | func (c *Client) getDatabaseStatus(ctx context.Context, databaseID string) (*qovery.Status, *apierrors.APIError) { 12 | status, res, err := c.api.DatabaseMainCallsAPI. 13 | GetDatabaseStatus(ctx, databaseID). 14 | Execute() 15 | if err != nil || res.StatusCode >= 400 { 16 | return nil, apierrors.NewReadError(apierrors.APIResourceDatabaseStatus, databaseID, res, err) 17 | } 18 | 19 | // Handle READY as STOPPED state 20 | if status.State == qovery.STATEENUM_READY { 21 | status.State = qovery.STATEENUM_STOPPED 22 | } 23 | return status, nil 24 | } 25 | 26 | func (c *Client) updateDatabaseStatus(ctx context.Context, database *qovery.Database, desiredState qovery.StateEnum) (*qovery.Status, *apierrors.APIError) { 27 | // wait until we can stop the database - otherwise it will fail 28 | checker := newDatabaseFinalStateCheckerWaitFunc(c, database.Id) 29 | if apiErr := wait(ctx, checker, nil); apiErr != nil { 30 | return nil, apiErr 31 | } 32 | 33 | envChecker := newEnvironmentFinalStateCheckerWaitFunc(c, database.Environment.Id) 34 | if apiErr := wait(ctx, envChecker, nil); apiErr != nil { 35 | return nil, apiErr 36 | } 37 | 38 | // Wrap status call with retry logic to handle transient errors (DNS failures, timeouts, etc.) 39 | var status *qovery.Status 40 | apiErr := retryAPICall(ctx, func(ctx context.Context) *apierrors.APIError { 41 | var err *apierrors.APIError 42 | status, err = c.getDatabaseStatus(ctx, database.Id) 43 | return err 44 | }) 45 | if apiErr != nil { 46 | return nil, apiErr 47 | } 48 | 49 | if status.State != desiredState { 50 | switch desiredState { 51 | case qovery.STATEENUM_DEPLOYED: 52 | return c.deployDatabase(ctx, database.Id) 53 | case qovery.STATEENUM_STOPPED: 54 | return c.stopDatabase(ctx, database.Id) 55 | } 56 | } 57 | 58 | if status.ServiceDeploymentStatus == qovery.SERVICEDEPLOYMENTSTATUSENUM_OUT_OF_DATE { 59 | return c.redeployDatabase(ctx, database.Id) 60 | } 61 | 62 | return status, nil 63 | } 64 | --------------------------------------------------------------------------------