├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── README.md ├── docs ├── data-sources │ ├── azuread_service_principal.md │ ├── azuread_user.md │ ├── database.md │ ├── database_permissions.md │ ├── database_role.md │ ├── database_roles.md │ ├── databases.md │ ├── query.md │ ├── schema.md │ ├── schema_permissions.md │ ├── schemas.md │ ├── server_permissions.md │ ├── server_role.md │ ├── server_roles.md │ ├── sql_login.md │ ├── sql_logins.md │ ├── sql_user.md │ └── sql_users.md ├── index.md └── resources │ ├── azuread_service_principal.md │ ├── azuread_user.md │ ├── database.md │ ├── database_permission.md │ ├── database_role.md │ ├── database_role_member.md │ ├── schema.md │ ├── schema_permission.md │ ├── script.md │ ├── server_permission.md │ ├── server_role.md │ ├── server_role_member.md │ ├── sql_login.md │ └── sql_user.md ├── examples ├── data-sources │ ├── mssql_azuread_service_principal │ │ └── data-source.tf │ ├── mssql_azuread_user │ │ └── data-source.tf │ ├── mssql_database │ │ └── data-source.tf │ ├── mssql_database_permissions │ │ └── data-source.tf │ ├── mssql_database_role │ │ └── data-source.tf │ ├── mssql_database_roles │ │ └── data-source.tf │ ├── mssql_databases │ │ └── data-source.tf │ ├── mssql_query │ │ └── data-source.tf │ ├── mssql_schema │ │ └── data-source.tf │ ├── mssql_schema_permissions │ │ └── data-source.tf │ ├── mssql_schemas │ │ └── data-source.tf │ ├── mssql_server_permissions │ │ └── data-source.tf │ ├── mssql_server_role │ │ └── data-source.tf │ ├── mssql_server_roles │ │ └── data-source.tf │ ├── mssql_sql_login │ │ └── data-source.tf │ ├── mssql_sql_logins │ │ └── data-source.tf │ ├── mssql_sql_user │ │ └── data-source.tf │ └── mssql_sql_users │ │ └── data-source.tf ├── provider │ ├── aad_default.tf │ ├── aad_sp.tf │ ├── azure_sql.tf │ └── sql.tf └── resources │ ├── mssql_azuread_service_principal │ ├── import.sh │ └── resource.tf │ ├── mssql_azuread_user │ ├── import.sh │ └── resource.tf │ ├── mssql_database │ ├── import.sh │ └── resource.tf │ ├── mssql_database_permission │ ├── import.sh │ └── resource.tf │ ├── mssql_database_role │ ├── import.sh │ └── resource.tf │ ├── mssql_database_role_member │ ├── import.sh │ └── resource.tf │ ├── mssql_schema │ ├── import.sh │ └── resource.tf │ ├── mssql_schema_permission │ ├── import.sh │ └── resource.tf │ ├── mssql_script │ └── resource.tf │ ├── mssql_server_permission │ ├── import.sh │ └── resource.tf │ ├── mssql_server_role │ ├── import.sh │ └── resource.tf │ ├── mssql_server_role_member │ ├── import.sh │ └── resource.tf │ ├── mssql_sql_login │ ├── import.sh │ └── resource.tf │ └── mssql_sql_user │ ├── import.sh │ └── resource.tf ├── go.mod ├── go.sum ├── internal ├── acctest │ └── testContext.go ├── core │ ├── attrs │ │ ├── compositeId.go │ │ ├── numericId.go │ │ └── permissionId.go │ ├── datasource │ │ ├── datasource.go │ │ └── datasourceWrapper.go │ ├── resource │ │ ├── resource.go │ │ └── resourceWrapper.go │ ├── resourceContext.go │ └── service.go ├── planModifiers │ ├── ignoreCaseModifier.go │ └── ignoreCaseModifier_test.go ├── provider │ ├── provider.go │ ├── providerData.go │ ├── providerData_test.go │ ├── services.go │ └── services_test.go ├── services │ ├── azureADServicePrincipal │ │ ├── base.go │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── azureADUser │ │ ├── base.go │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── common │ │ ├── attributes.go │ │ ├── helpers.go │ │ └── types.go │ ├── database │ │ ├── base.go │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── databasePermission │ │ ├── base.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── databaseRole │ │ ├── base.go │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── databaseRoleMember │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── schema │ │ ├── base.go │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── schemaPermission │ │ ├── base.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── script │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── serverPermission │ │ ├── base.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── serverRole │ │ ├── base.go │ │ ├── common_acctest.go │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── serverRoleMember │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ ├── sqlLogin │ │ ├── base.go │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go │ └── sqlUser │ │ ├── base.go │ │ ├── data.go │ │ ├── data_acctest.go │ │ ├── list.go │ │ ├── list_acctest.go │ │ ├── main.go │ │ ├── resource.go │ │ └── resource_acctest.go ├── sql │ ├── azureAuth.go │ ├── azureAuth_test.go │ ├── connection.go │ ├── connectionMock_test.go │ ├── connection_test.go │ ├── database.go │ ├── databaseRole.go │ ├── databaseRole_test.go │ ├── database_test.go │ ├── dbMock_test.go │ ├── ids.go │ ├── schema.go │ ├── schema_test.go │ ├── serverRole.go │ ├── serverRole_test.go │ ├── sqlAuth.go │ ├── sqlAuth_test.go │ ├── sqlLogin.go │ ├── sqlLogin_test.go │ ├── sqlTestSuite_test.go │ ├── user.go │ ├── user_test.go │ ├── utils.go │ └── utils_test.go ├── utils │ ├── diagnostics.go │ ├── errorHandling.go │ └── terraformDataBind.go └── validators │ ├── compositeValidators.go │ ├── sqlIdentifier.go │ ├── sqlIdentifier_test.go │ ├── stringLength.go │ ├── stringLength_test.go │ └── utils_test.go ├── main.go ├── templates └── index.md.tmpl ├── terraform-registry-manifest.json ├── tests ├── azure_test_harness │ ├── .terraform.lock.hcl │ ├── data.tf │ ├── env.tpl │ ├── main.tf │ ├── msi.tf │ ├── outputs.tf │ ├── providers.tf │ └── server.tf └── docker_test_harness │ ├── .terraform.lock.hcl │ ├── env.tpl │ ├── main.tf │ ├── outputs.tf │ ├── providers.tf │ └── variables.tf └── tools └── tools.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "gomod" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: write 10 | 11 | env: 12 | GO_VERSION: '1.19' 13 | 14 | jobs: 15 | goreleaser: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | # Allow goreleaser to access older tag information. 21 | fetch-depth: 0 22 | - uses: actions/setup-go@v3 23 | with: 24 | go-version: ${{ env.GO_VERSION }} 25 | - name: Import GPG key 26 | id: import_gpg 27 | uses: crazy-max/ghaction-import-gpg@v5 28 | with: 29 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 30 | passphrase: "${{ secrets.GPG_PASSPHRASE }}" 31 | - name: Run GoReleaser 32 | uses: goreleaser/goreleaser-action@v4 33 | with: 34 | args: release --rm-dist 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | .idea/ 17 | terraform.tfstate* 18 | .vscode/ 19 | local.env 20 | .terraform/ 21 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | mod_timestamp: '{{ .CommitTimestamp }}' 5 | flags: 6 | - -trimpath 7 | ldflags: 8 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 9 | goos: 10 | - windows 11 | - linux 12 | - darwin 13 | goarch: 14 | - amd64 15 | - '386' 16 | - arm 17 | - arm64 18 | ignore: 19 | - goos: darwin 20 | goarch: '386' 21 | binary: '{{ .ProjectName }}_v{{ .Version }}' 22 | 23 | archives: 24 | - format: zip 25 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 26 | 27 | checksum: 28 | extra_files: 29 | - glob: 'terraform-registry-manifest.json' 30 | name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 31 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 32 | algorithm: sha256 33 | 34 | signs: 35 | - artifacts: checksum 36 | args: 37 | - "--batch" 38 | - "--local-user" 39 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 40 | - "--output" 41 | - "${signature}" 42 | - "--detach-sign" 43 | - "${artifact}" 44 | release: 45 | extra_files: 46 | - glob: 'terraform-registry-manifest.json' 47 | name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json' 48 | 49 | changelog: 50 | skip: true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 tkielar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | AZURE_HARNESS := ./tests/azure_test_harness 4 | DOCKER_HARNESS := ./tests/docker_test_harness 5 | 6 | tidy: 7 | go mod tidy 8 | 9 | build: tidy 10 | go build -v . 11 | 12 | test: tidy 13 | go test -v ./internal/... 14 | 15 | install: tidy 16 | go install 17 | 18 | accTest: 19 | if [ -f local.env ]; then source ./local.env; fi && TF_ACC=1 go test -count 1 -timeout 60m -v ./internal/... 20 | 21 | setupAccTest: 22 | cd $(MAKE_PATH); terraform init -reconfigure && terraform apply -auto-approve 23 | 24 | destroyAccTest: 25 | cd $(MAKE_PATH) && terraform destroy -auto-approve 26 | 27 | accTestLifecycle: 28 | $(MAKE) accTest; TEST_CODE=$$?; $(MAKE) $(MAKE_DESTROY_TARGET) && exit $$TEST_CODE 29 | 30 | # AZURE 31 | azureSetupAccTest: 32 | MAKE_PATH=$(AZURE_HARNESS) $(MAKE) setupAccTest 33 | 34 | azureDestroyAccTest: 35 | cd $(AZURE_HARNESS) && terraform init -reconfigure && (terraform state rm azurerm_mssql_elasticpool.this; terraform destroy -auto-approve) 36 | 37 | azureAccTest: azureSetupAccTest 38 | MAKE_DESTROY_TARGET=azureDestroyAccTest $(MAKE) accTestLifecycle 39 | 40 | 41 | #DOCKER 42 | dockerSetupAccTest: 43 | MAKE_PATH=$(DOCKER_HARNESS) $(MAKE) setupAccTest 44 | 45 | dockerDestroyAccTest: 46 | MAKE_PATH=$(DOCKER_HARNESS) $(MAKE) destroyAccTest 47 | 48 | dockerAccTest: dockerSetupAccTest 49 | MAKE_DESTROY_TARGET=dockerDestroyAccTest $(MAKE) accTestLifecycle 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform provider for MS SQL Server 2 | 3 | - [Usage docs](docs/index.md) 4 | - [Examples](examples/) 5 | 6 | ## Requirements 7 | - [Terraform](https://www.terraform.io/downloads) >=1.0.0 8 | - MS SQL Server >=2017 or Azure SQL 9 | - [Go](https://go.dev/doc/install) 1.19 (to build the provider) 10 | 11 | ## Building the provider 12 | Clone repository to: `$GOPATH/src/github.com/PGSSoft/terraform-provider-mssql` 13 | 14 | ```shell 15 | $ cd $GOPATH/src/github.com/PGSSoft/terraform-provider-mssql 16 | $ make install 17 | ``` 18 | 19 | ## Acceptance tests 20 | Acceptance tests run MS SQL Server docker container automatically. 21 | Make sure you have Docker installed and configured before running the tests (i.e. make sure you're able to run `docker run `). 22 | 23 | To run all tests, including acceptance tests, using Docker MSSQL image: 24 | ```shell 25 | $ make dockerAccTest 26 | ``` 27 | 28 | To run all tests, including acceptance tests, using Azure SQL: 29 | ```shell 30 | $ az login 31 | $ make azureAccTest 32 | ``` 33 | 34 | 35 | To run only unit tests (excluding tests depending on Docker container or Azure SQL): 36 | ```shell 37 | $ make test 38 | ``` 39 | 40 | ## About 41 | 42 | The project maintained by [software development agency](https://www.pgs-soft.com/) [PGS Software](https://www.pgs-soft.com/). 43 | See our other [open-source projects](https://github.com/PGSSoft) or [contact us](https://www.pgs-soft.com/contact-us/) to develop your product. 44 | 45 | 46 | ## Follow us 47 | 48 | [![Twitter URL](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=https://github.com/PGSSoft/InAppPurchaseButton) 49 | [![Twitter Follow](https://img.shields.io/twitter/follow/pgssoftware.svg?style=social&label=Follow)](https://twitter.com/pgssoftware) -------------------------------------------------------------------------------- /docs/data-sources/azuread_service_principal.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_azuread_service_principal Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about single Azure AD Service Principal database user. 7 | --- 8 | 9 | # mssql_azuread_service_principal (Data Source) 10 | 11 | Obtains information about single Azure AD Service Principal database user. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_azuread_service_principal" "example" { 21 | name = "example" 22 | database_id = data.mssql_database.example.id 23 | } 24 | 25 | output "app_client_id" { 26 | value = data.mssql_azuread_service_principal.example.client_id 27 | } 28 | ``` 29 | 30 | 31 | ## Schema 32 | 33 | ### Required 34 | 35 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 36 | 37 | ### Optional 38 | 39 | - `client_id` (String) Azure AD client_id of the Service Principal. This can be either regular Service Principal or Managed Service Identity. 40 | - `name` (String) User name. Cannot be longer than 128 chars. 41 | 42 | ### Read-Only 43 | 44 | - `id` (String) `/`. User ID can be retrieved using `sys.database_principals` view. 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/data-sources/azuread_user.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_azuread_user Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about single Azure AD database user. 7 | --- 8 | 9 | # mssql_azuread_user (Data Source) 10 | 11 | Obtains information about single Azure AD database user. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_azuread_user" "example" { 21 | name = "example" 22 | database_id = data.mssql_database.example.id 23 | } 24 | 25 | output "user_object_id" { 26 | value = data.mssql_azuread_user.example.user_object_id 27 | } 28 | ``` 29 | 30 | 31 | ## Schema 32 | 33 | ### Required 34 | 35 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 36 | 37 | ### Optional 38 | 39 | - `name` (String) User name. Cannot be longer than 128 chars. 40 | - `user_object_id` (String) Azure AD object_id of the user. This can be either regular user or a group. 41 | 42 | ### Read-Only 43 | 44 | - `id` (String) `/`. User ID can be retrieved using `sys.database_principals` view. 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/data-sources/database.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_database Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about single database. 7 | --- 8 | 9 | # mssql_database (Data Source) 10 | 11 | Obtains information about single database. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | output "db_id" { 21 | value = data.mssql_database.example.id 22 | } 23 | 24 | output "db_collation" { 25 | value = data.mssql_database.example.collation 26 | } 27 | ``` 28 | 29 | 30 | ## Schema 31 | 32 | ### Required 33 | 34 | - `name` (String) Database name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers). 35 | 36 | ### Read-Only 37 | 38 | - `collation` (String) Default collation name. Can be either a Windows collation name or a SQL collation name. 39 | - `id` (String) Database ID. Can be retrieved using `SELECT DB_ID('')`. 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/data-sources/database_permissions.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_database_permissions Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Returns all permissions granted in a DB to given principal 7 | --- 8 | 9 | # mssql_database_permissions (Data Source) 10 | 11 | Returns all permissions granted in a DB to given principal 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_sql_user" "example" { 21 | name = "example_user" 22 | database_id = data.mssql_database.example.id 23 | } 24 | 25 | data "mssql_database_permissions" "example" { 26 | principal_id = data.mssql_sql_user.example.id 27 | } 28 | 29 | output "permissions" { 30 | value = data.mssql_database_permissions.example.permissions 31 | } 32 | ``` 33 | 34 | 35 | ## Schema 36 | 37 | ### Required 38 | 39 | - `principal_id` (String) `/`. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal`. 40 | 41 | ### Read-Only 42 | 43 | - `id` (String) `/`. 44 | - `permissions` (Attributes Set) Set of permissions granted to the principal (see [below for nested schema](#nestedatt--permissions)) 45 | 46 | 47 | ### Nested Schema for `permissions` 48 | 49 | Read-Only: 50 | 51 | - `permission` (String) Name of database-level SQL permission. For full list of supported permissions, see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql?view=azuresqldb-current#remarks) 52 | - `with_grant_option` (Boolean) When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals. 53 | 54 | 55 | -------------------------------------------------------------------------------- /docs/data-sources/database_role.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_database_role Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about single database role. 7 | --- 8 | 9 | # mssql_database_role (Data Source) 10 | 11 | Obtains information about single database role. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "master" { 17 | name = "master" 18 | } 19 | 20 | data "mssql_database_role" "example" { 21 | name = "public" 22 | database_id = data.mssql_database.master.id 23 | } 24 | 25 | output "id" { 26 | value = data.mssql_database_role.example.id 27 | } 28 | ``` 29 | 30 | 31 | ## Schema 32 | 33 | ### Required 34 | 35 | - `name` (String) Role name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers) and cannot be longer than 128 chars. 36 | 37 | ### Optional 38 | 39 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. Defaults to ID of `master`. 40 | 41 | ### Read-Only 42 | 43 | - `id` (String) `/`. Role ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')` 44 | - `members` (Attributes Set) Set of role members (see [below for nested schema](#nestedatt--members)) 45 | - `owner_id` (String) ID of another database role or user owning this role. Can be retrieved using `mssql_database_role` or `mssql_sql_user`. 46 | 47 | 48 | ### Nested Schema for `members` 49 | 50 | Read-Only: 51 | 52 | - `id` (String) `/`. Member ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('') 53 | - `name` (String) Name of the database principal. 54 | - `type` (String) One of: `SQL_USER`, `DATABASE_ROLE`, `AZUREAD_USER` 55 | 56 | 57 | -------------------------------------------------------------------------------- /docs/data-sources/database_roles.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_database_roles Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about all roles defined in a database. 7 | --- 8 | 9 | # mssql_database_roles (Data Source) 10 | 11 | Obtains information about all roles defined in a database. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "master" { 17 | name = "master" 18 | } 19 | 20 | data "mssql_database_roles" "example" { 21 | database_id = data.mssql_database.master.id 22 | } 23 | 24 | output "roles" { 25 | value = data.mssql_database_roles.example.roles 26 | } 27 | ``` 28 | 29 | 30 | ## Schema 31 | 32 | ### Optional 33 | 34 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. Defaults to ID of `master`. 35 | 36 | ### Read-Only 37 | 38 | - `id` (String) ID of the resource, equals to database ID 39 | - `roles` (Attributes Set) Set of database role objects (see [below for nested schema](#nestedatt--roles)) 40 | 41 | 42 | ### Nested Schema for `roles` 43 | 44 | Read-Only: 45 | 46 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 47 | - `id` (String) `/`. Role ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')` 48 | - `name` (String) Role name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers) and cannot be longer than 128 chars. 49 | - `owner_id` (String) ID of another database role or user owning this role. Can be retrieved using `mssql_database_role` or `mssql_sql_user`. 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/data-sources/databases.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_databases Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about all databases found in SQL Server instance. 7 | --- 8 | 9 | # mssql_databases (Data Source) 10 | 11 | Obtains information about all databases found in SQL Server instance. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_databases" "example" {} 17 | 18 | output "databases" { 19 | value = data.mssql_databases.example.databases 20 | } 21 | ``` 22 | 23 | 24 | ## Schema 25 | 26 | ### Read-Only 27 | 28 | - `databases` (Attributes Set) Set of database objects (see [below for nested schema](#nestedatt--databases)) 29 | - `id` (String) ID of the resource used only internally by the provider. 30 | 31 | 32 | ### Nested Schema for `databases` 33 | 34 | Read-Only: 35 | 36 | - `collation` (String) Default collation name. Can be either a Windows collation name or a SQL collation name. 37 | - `id` (String) Database ID. Can be retrieved using `SELECT DB_ID('')`. 38 | - `name` (String) Database name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers). 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/data-sources/query.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_query Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Retrieves arbitrary SQL query result. 7 | -> Note This data source is meant to be an escape hatch for all cases not supported by the provider's data sources. Whenever possible, use dedicated data sources, which offer better plan, validation and error reporting. 8 | --- 9 | 10 | # mssql_query (Data Source) 11 | 12 | Retrieves arbitrary SQL query result. 13 | 14 | -> **Note** This data source is meant to be an escape hatch for all cases not supported by the provider's data sources. Whenever possible, use dedicated data sources, which offer better plan, validation and error reporting. 15 | 16 | ## Example Usage 17 | 18 | ```terraform 19 | data "mssql_database" "test" { 20 | name = "test" 21 | } 22 | 23 | data "mssql_query" "column" { 24 | database_id = data.mssql_database.test.id 25 | query = "SELECT [column_id], [name] FROM sys.columns WHERE [object_id] = OBJECT_ID('test_table')" 26 | } 27 | 28 | output "column_names" { 29 | value = data.mssql_query.column.result[*].name 30 | } 31 | ``` 32 | 33 | 34 | ## Schema 35 | 36 | ### Required 37 | 38 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 39 | - `query` (String) SQL query returning single result set, with any number of rows, where all columns are strings 40 | 41 | ### Read-Only 42 | 43 | - `id` (String) Used only internally by Terraform. Always set to `query` 44 | - `result` (List of Map of String) Results of the SQL query, represented as list of maps, where the map key corresponds to column name and the value is the value of column in given row. 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/data-sources/schema.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_schema Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Retrieves information about DB schema. 7 | --- 8 | 9 | # mssql_schema (Data Source) 10 | 11 | Retrieves information about DB schema. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_schema" "by_name" { 21 | database_id = data.mssql_database.example.id 22 | name = "dbo" 23 | } 24 | ``` 25 | 26 | 27 | ## Schema 28 | 29 | ### Optional 30 | 31 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 32 | - `id` (String) `/`. Schema ID can be retrieved using `SELECT SCHEMA_ID('')`. Either `id` or `name` must be provided. 33 | - `name` (String) Schema name. Either `id` or `name` must be provided. 34 | 35 | ### Read-Only 36 | 37 | - `owner_id` (String) ID of database role or user owning this schema. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal` 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/data-sources/schema_permissions.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_schema_permissions Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Returns all permissions granted in a schema to given principal 7 | --- 8 | 9 | # mssql_schema_permissions (Data Source) 10 | 11 | Returns all permissions granted in a schema to given principal 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_sql_user" "example" { 21 | name = "example_user" 22 | database_id = data.mssql_database.example.id 23 | } 24 | 25 | data "mssql_schema" "example" { 26 | name = "example_schema" 27 | database_id = data.mssql_database.example.id 28 | } 29 | 30 | data "mssql_schema_permissions" "example" { 31 | schema_id = data.mssql_schema.example.id 32 | principal_id = data.mssql_sql_user.example.id 33 | } 34 | 35 | output "permissions" { 36 | value = data.mssql_schema_permissions.example.permissions 37 | } 38 | ``` 39 | 40 | 41 | ## Schema 42 | 43 | ### Required 44 | 45 | - `principal_id` (String) `/`. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal`. 46 | - `schema_id` (String) `/`. Can be retrieved using `mssql_schema`. 47 | 48 | ### Read-Only 49 | 50 | - `id` (String) `//`. 51 | - `permissions` (Attributes Set) Set of permissions granted to the principal (see [below for nested schema](#nestedatt--permissions)) 52 | 53 | 54 | ### Nested Schema for `permissions` 55 | 56 | Read-Only: 57 | 58 | - `permission` (String) Name of schema SQL permission. For full list of supported permissions, see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-schema-permissions-transact-sql?view=azuresqldb-current#remarks) 59 | - `with_grant_option` (Boolean) When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals. 60 | 61 | 62 | -------------------------------------------------------------------------------- /docs/data-sources/schemas.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_schemas Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about all schemas found in SQL database. 7 | --- 8 | 9 | # mssql_schemas (Data Source) 10 | 11 | Obtains information about all schemas found in SQL database. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_schemas" "all" { 21 | database_id = data.mssql_database.example.id 22 | } 23 | 24 | output "all_schema_names" { 25 | value = data.mssql_schemas.all.schemas[*].name 26 | } 27 | ``` 28 | 29 | 30 | ## Schema 31 | 32 | ### Optional 33 | 34 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. Defaults to ID of `master`. 35 | 36 | ### Read-Only 37 | 38 | - `id` (String) ID of the data source, equals to database ID 39 | - `schemas` (Attributes Set) Set of schemas found in the DB. (see [below for nested schema](#nestedatt--schemas)) 40 | 41 | 42 | ### Nested Schema for `schemas` 43 | 44 | Read-Only: 45 | 46 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 47 | - `id` (String) `/`. Schema ID can be retrieved using `SELECT SCHEMA_ID('')`. 48 | - `name` (String) Schema name. 49 | - `owner_id` (String) ID of database role or user owning this schema. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal` 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/data-sources/server_permissions.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_server_permissions Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Returns all permissions grated to given principal 7 | --- 8 | 9 | # mssql_server_permissions (Data Source) 10 | 11 | Returns all permissions grated to given principal 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_sql_login" "example" { 17 | name = "example_login" 18 | } 19 | 20 | data "mssql_server_permissions" "example" { 21 | principal_id = data.mssql_sql_login.example.principal_id 22 | } 23 | 24 | output "permissions" { 25 | value = data.mssql_server_permissions.example.permissions 26 | } 27 | ``` 28 | 29 | 30 | ## Schema 31 | 32 | ### Required 33 | 34 | - `principal_id` (String) ID of the principal who will be granted `permission`. Can be retrieved using `mssql_server_role` or `mssql_sql_login`. 35 | 36 | ### Read-Only 37 | 38 | - `id` (String) Equals to `principal_id`. 39 | - `permissions` (Attributes Set) Set of permissions granted to the principal (see [below for nested schema](#nestedatt--permissions)) 40 | 41 | 42 | ### Nested Schema for `permissions` 43 | 44 | Read-Only: 45 | 46 | - `permission` (String) Name of server-level SQL permission. For full list of supported permissions see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-server-permissions-transact-sql?view=azuresqldb-current#remarks) 47 | - `with_grant_option` (Boolean) When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals. 48 | 49 | 50 | -------------------------------------------------------------------------------- /docs/data-sources/server_role.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_server_role Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about single server role. 7 | --- 8 | 9 | # mssql_server_role (Data Source) 10 | 11 | Obtains information about single server role. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_server_role" "by_name" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_server_role" "by_id" { 21 | id = 8 22 | } 23 | ``` 24 | 25 | 26 | ## Schema 27 | 28 | ### Optional 29 | 30 | - `id` (String) Role principal ID. Either `name` or `id` must be provided. 31 | - `name` (String) Role name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers) and cannot be longer than 128 chars. Either `name` or `id` must be provided. 32 | 33 | ### Read-Only 34 | 35 | - `members` (Attributes Set) Set of role members (see [below for nested schema](#nestedatt--members)) 36 | - `owner_id` (String) ID of another server role or login owning this role. Can be retrieved using `mssql_server_role` or `mssql_sql_login`. 37 | 38 | 39 | ### Nested Schema for `members` 40 | 41 | Read-Only: 42 | 43 | - `id` (String) ID of the member principal 44 | - `name` (String) Name of the server principal 45 | - `type` (String) One of: `SQL_LOGIN`, `SERVER_ROLE` 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/data-sources/server_roles.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_server_roles Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about all roles defined in the server. 7 | --- 8 | 9 | # mssql_server_roles (Data Source) 10 | 11 | Obtains information about all roles defined in the server. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_server_roles" "all" {} 17 | 18 | output "roles" { 19 | value = data.mssql_server_roles.all.roles 20 | } 21 | ``` 22 | 23 | 24 | ## Schema 25 | 26 | ### Read-Only 27 | 28 | - `id` (String) Only used internally in Terraform. Always set to `server_roles`. 29 | - `roles` (Attributes Set) Set of all roles found in the server (see [below for nested schema](#nestedatt--roles)) 30 | 31 | 32 | ### Nested Schema for `roles` 33 | 34 | Read-Only: 35 | 36 | - `id` (String) Role principal ID. 37 | - `name` (String) Role name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers) and cannot be longer than 128 chars. 38 | - `owner_id` (String) ID of another server role or login owning this role. Can be retrieved using `mssql_server_role` or `mssql_sql_login`. 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/data-sources/sql_login.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_sql_login Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about single SQL login. 7 | --- 8 | 9 | # mssql_sql_login (Data Source) 10 | 11 | Obtains information about single SQL login. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_sql_login" "sa" { 17 | name = "sa" 18 | } 19 | 20 | output "id" { 21 | value = data.mssql_sql_login.sa.id 22 | } 23 | 24 | output "db_id" { 25 | value = data.mssql_sql_login.sa.default_database_id 26 | } 27 | ``` 28 | 29 | 30 | ## Schema 31 | 32 | ### Required 33 | 34 | - `name` (String) Login name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers) and cannot contain `\ ` 35 | 36 | ### Read-Only 37 | 38 | - `check_password_expiration` (Boolean) When `true`, password expiration policy is enforced for this login. 39 | - `check_password_policy` (Boolean) When `true`, the Windows password policies of the computer on which SQL Server is running are enforced on this login. 40 | - `default_database_id` (String) ID of login's default DB. The ID can be retrieved using `mssql_database` data resource. 41 | - `default_language` (String) Default language assigned to login. 42 | - `id` (String) Login SID. Can be retrieved using `SELECT SUSER_SID('')`. 43 | - `must_change_password` (Boolean) When true, password change will be forced on first logon. 44 | - `principal_id` (String) ID used to reference SQL Login in other resources, e.g. `server_role`. Can be retrieved from `sys.sql_logins`. 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/data-sources/sql_logins.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_sql_logins Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about all SQL logins found in SQL Server instance. 7 | --- 8 | 9 | # mssql_sql_logins (Data Source) 10 | 11 | Obtains information about all SQL logins found in SQL Server instance. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_sql_logins" "example" {} 17 | 18 | output "databases" { 19 | value = data.mssql_sql_logins.example.logins 20 | } 21 | ``` 22 | 23 | 24 | ## Schema 25 | 26 | ### Read-Only 27 | 28 | - `id` (String) ID of the resource used only internally by the provider. 29 | - `logins` (Attributes Set) Set of SQL login objects (see [below for nested schema](#nestedatt--logins)) 30 | 31 | 32 | ### Nested Schema for `logins` 33 | 34 | Read-Only: 35 | 36 | - `check_password_expiration` (Boolean) When `true`, password expiration policy is enforced for this login. 37 | - `check_password_policy` (Boolean) When `true`, the Windows password policies of the computer on which SQL Server is running are enforced on this login. 38 | - `default_database_id` (String) ID of login's default DB. The ID can be retrieved using `mssql_database` data resource. 39 | - `default_language` (String) Default language assigned to login. 40 | - `id` (String) Login SID. Can be retrieved using `SELECT SUSER_SID('')`. 41 | - `must_change_password` (Boolean) When true, password change will be forced on first logon. 42 | - `name` (String) Login name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers) and cannot contain `\ ` 43 | - `principal_id` (String) ID used to reference SQL Login in other resources, e.g. `server_role`. Can be retrieved from `sys.sql_logins`. 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/data-sources/sql_user.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_sql_user Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about single SQL database user. 7 | --- 8 | 9 | # mssql_sql_user (Data Source) 10 | 11 | Obtains information about single SQL database user. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "master" { 17 | name = "master" 18 | } 19 | 20 | data "mssql_sql_user" "example" { 21 | name = "dbo" 22 | database_id = data.mssql_database.master.id 23 | } 24 | 25 | output "id" { 26 | value = data.mssql_sql_user.example.id 27 | } 28 | ``` 29 | 30 | 31 | ## Schema 32 | 33 | ### Required 34 | 35 | - `name` (String) User name. Cannot be longer than 128 chars. 36 | 37 | ### Optional 38 | 39 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 40 | 41 | ### Read-Only 42 | 43 | - `id` (String) `/`. User ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')`. 44 | - `login_id` (String) SID of SQL login. Can be retrieved using `mssql_sql_login` or `SELECT SUSER_SID('')`. 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/data-sources/sql_users.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_sql_users Data Source - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Obtains information about all SQL users found in a database 7 | --- 8 | 9 | # mssql_sql_users (Data Source) 10 | 11 | Obtains information about all SQL users found in a database 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "master" { 17 | name = "master" 18 | } 19 | 20 | data "mssql_sql_users" "example" { 21 | database_id = data.mssql_database.master.id 22 | } 23 | 24 | output "users" { 25 | value = data.mssql_sql_users.example.users 26 | } 27 | ``` 28 | 29 | 30 | ## Schema 31 | 32 | ### Optional 33 | 34 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. Defaults to ID of `master`. 35 | 36 | ### Read-Only 37 | 38 | - `id` (String) ID of the resource, equals to database ID 39 | - `users` (Attributes Set) Set of SQL user objects (see [below for nested schema](#nestedatt--users)) 40 | 41 | 42 | ### Nested Schema for `users` 43 | 44 | Read-Only: 45 | 46 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 47 | - `id` (String) `/`. User ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')`. 48 | - `login_id` (String) SID of SQL login. Can be retrieved using `mssql_sql_login` or `SELECT SUSER_SID('')`. 49 | - `name` (String) User name. Cannot be longer than 128 chars. 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/resources/azuread_service_principal.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_azuread_service_principal Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Managed database-level user mapped to Azure AD identity (service principal or managed identity). 7 | -> Note When using this resource, Azure SQL server managed identity does not need any AzureAD role assignments https://docs.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-service-principal?view=azuresql. 8 | --- 9 | 10 | # mssql_azuread_service_principal (Resource) 11 | 12 | Managed database-level user mapped to Azure AD identity (service principal or managed identity). 13 | 14 | -> **Note** When using this resource, Azure SQL server managed identity does not need any [AzureAD role assignments](https://docs.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-service-principal?view=azuresql). 15 | 16 | ## Example Usage 17 | 18 | ```terraform 19 | data "mssql_database" "example" { 20 | name = "example" 21 | } 22 | 23 | data "azuread_service_principal" "example" { 24 | display_name = "test-application" 25 | } 26 | 27 | resource "mssql_azuread_service_principal" "example" { 28 | name = "example" 29 | database_id = data.mssql_database.example.id 30 | client_id = data.azuread_service_principal.example.application_id 31 | } 32 | 33 | output "user_id" { 34 | value = mssql_azuread_service_principal.example.id 35 | } 36 | ``` 37 | 38 | 39 | ## Schema 40 | 41 | ### Required 42 | 43 | - `client_id` (String) Azure AD client_id of the Service Principal. This can be either regular Service Principal or Managed Service Identity. 44 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 45 | - `name` (String) User name. Cannot be longer than 128 chars. 46 | 47 | ### Read-Only 48 | 49 | - `id` (String) `/`. User ID can be retrieved using `sys.database_principals` view. 50 | 51 | ## Import 52 | 53 | Import is supported using the following syntax: 54 | 55 | ```shell 56 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', principal_id) FROM sys.database_principals WHERE [name] = ''` 57 | terraform import mssql_azuread_service_principal.example '7/5' 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/resources/azuread_user.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_azuread_user Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Managed database-level user mapped to Azure AD identity (user or group). 7 | -> Note When using this resource, Azure SQL server managed identity does not need any AzureAD role assignments https://docs.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-service-principal?view=azuresql. 8 | --- 9 | 10 | # mssql_azuread_user (Resource) 11 | 12 | Managed database-level user mapped to Azure AD identity (user or group). 13 | 14 | -> **Note** When using this resource, Azure SQL server managed identity does not need any [AzureAD role assignments](https://docs.microsoft.com/en-us/azure/azure-sql/database/authentication-aad-service-principal?view=azuresql). 15 | 16 | ## Example Usage 17 | 18 | ```terraform 19 | data "mssql_database" "example" { 20 | name = "example" 21 | } 22 | 23 | data "azuread_user" "example" { 24 | user_principal_name = "user@example.com" 25 | } 26 | 27 | resource "mssql_azuread_user" "example" { 28 | name = "example" 29 | database_id = data.mssql_database.example.id 30 | user_object_id = data.azuread_user.example.object_id 31 | } 32 | 33 | output "user_id" { 34 | value = mssql_azuread_user.example.id 35 | } 36 | ``` 37 | 38 | 39 | ## Schema 40 | 41 | ### Required 42 | 43 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 44 | - `name` (String) User name. Cannot be longer than 128 chars. 45 | - `user_object_id` (String) Azure AD object_id of the user. This can be either regular user or a group. 46 | 47 | ### Read-Only 48 | 49 | - `id` (String) `/`. User ID can be retrieved using `sys.database_principals` view. 50 | 51 | ## Import 52 | 53 | Import is supported using the following syntax: 54 | 55 | ```shell 56 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', principal_id) FROM sys.database_principals WHERE [name] = ''` 57 | terraform import mssql_azuread_user.example '7/5' 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/resources/database.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_database Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Manages single database. 7 | --- 8 | 9 | # mssql_database (Resource) 10 | 11 | Manages single database. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | resource "mssql_database" "example" { 17 | name = "example" 18 | collation = "SQL_Latin1_General_CP1_CS_AS" 19 | } 20 | ``` 21 | 22 | 23 | ## Schema 24 | 25 | ### Required 26 | 27 | - `name` (String) Database name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers). 28 | 29 | ### Optional 30 | 31 | - `collation` (String) Default collation name. Can be either a Windows collation name or a SQL collation name. Defaults to SQL Server instance's default collation. 32 | 33 | ### Read-Only 34 | 35 | - `id` (String) Database ID. Can be retrieved using `SELECT DB_ID('')`. 36 | 37 | ## Import 38 | 39 | Import is supported using the following syntax: 40 | 41 | ```shell 42 | # import using database ID - can be retrieved using `SELECT DB_ID('')` 43 | terraform import mssql_database.example 12 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/resources/database_permission.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_database_permission Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Grants database-level permission. 7 | --- 8 | 9 | # mssql_database_permission (Resource) 10 | 11 | Grants database-level permission. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_sql_user" "example" { 21 | name = "example_user" 22 | database_id = data.mssql_database.example.id 23 | } 24 | 25 | resource "mssql_database_permission" "delete_to_example" { 26 | principal_id = data.mssql_sql_user.example.id 27 | permission = "DELETE" 28 | } 29 | ``` 30 | 31 | 32 | ## Schema 33 | 34 | ### Required 35 | 36 | - `permission` (String) Name of database-level SQL permission. For full list of supported permissions, see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql?view=azuresqldb-current#remarks) 37 | - `principal_id` (String) `/`. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal`. 38 | 39 | ### Optional 40 | 41 | - `with_grant_option` (Boolean) When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals. Defaults to `false`. 42 | 43 | ### Read-Only 44 | 45 | - `id` (String) `//`. 46 | 47 | ## Import 48 | 49 | Import is supported using the following syntax: 50 | 51 | ```shell 52 | # import using // - can be retrieved using `SELECT CONCAT(DB_ID(), '/', DATABASE_PRINCIPAL_ID(''), '/DELETE')` 53 | terraform import mssql_database_permission.example '7/5/DELETE' 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/resources/database_role.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_database_role Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Manages database-level role. 7 | --- 8 | 9 | # mssql_database_role (Resource) 10 | 11 | Manages database-level role. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_sql_user" "owner" { 21 | name = "example_user" 22 | } 23 | 24 | resource "mssql_database_role" "example" { 25 | name = "example" 26 | database_id = data.mssql_database.example.id 27 | owner_id = data.mssql_sql_user.owner.id 28 | } 29 | ``` 30 | 31 | 32 | ## Schema 33 | 34 | ### Required 35 | 36 | - `name` (String) Role name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers) and cannot be longer than 128 chars. 37 | 38 | ### Optional 39 | 40 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. Defaults to ID of `master`. 41 | - `owner_id` (String) ID of another database role or user owning this role. Can be retrieved using `mssql_database_role` or `mssql_sql_user`. Defaults to ID of current user, used to authorize the Terraform provider. 42 | 43 | ### Read-Only 44 | 45 | - `id` (String) `/`. Role ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')` 46 | 47 | ## Import 48 | 49 | Import is supported using the following syntax: 50 | 51 | ```shell 52 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', DATABASE_PRINCIPAL_ID(''))` 53 | terraform import mssql_database_role.example '7/5' 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/resources/database_role_member.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_database_role_member Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Manages database role membership. 7 | --- 8 | 9 | # mssql_database_role_member (Resource) 10 | 11 | Manages database role membership. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_sql_user" "owner" { 21 | name = "example_user" 22 | database_id = data.mssql_database.example.id 23 | } 24 | 25 | data "mssql_sql_user" "member" { 26 | name = "member_user" 27 | database_id = data.mssql_database.example.id 28 | } 29 | 30 | resource "mssql_database_role" "example" { 31 | name = "example" 32 | database_id = data.mssql_database.example.id 33 | owner_id = data.mssql_sql_user.owner.id 34 | } 35 | 36 | resource "mssql_database_role_member" "example" { 37 | role_id = mssql_database_role.example.id 38 | member_id = data.mssql_sql_user.member.id 39 | } 40 | ``` 41 | 42 | 43 | ## Schema 44 | 45 | ### Required 46 | 47 | - `member_id` (String) Can be either user or role ID in format `/`. Can be retrieved using `mssql_sql_user` or `mssql_database_member`. 48 | - `role_id` (String) `/` 49 | 50 | ### Read-Only 51 | 52 | - `id` (String) `//`. Role and member IDs can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')` 53 | 54 | ## Import 55 | 56 | Import is supported using the following syntax: 57 | 58 | ```shell 59 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', DATABASE_PRINCIPAL_ID(''), '/', DATABASE_PRINCIPAL_ID(' 32 | ## Schema 33 | 34 | ### Required 35 | 36 | - `name` (String) Schema name. 37 | 38 | ### Optional 39 | 40 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. Defaults to ID of `master`. 41 | - `owner_id` (String) ID of database role or user owning this schema. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal` 42 | 43 | ### Read-Only 44 | 45 | - `id` (String) `/`. Schema ID can be retrieved using `SELECT SCHEMA_ID('')`. 46 | 47 | ## Import 48 | 49 | Import is supported using the following syntax: 50 | 51 | ```shell 52 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', SCHEMA_ID(''))` 53 | terraform import mssql_schema.example '7/5' 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/resources/schema_permission.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_schema_permission Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Grants database-level permission. 7 | --- 8 | 9 | # mssql_schema_permission (Resource) 10 | 11 | Grants database-level permission. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | data "mssql_sql_user" "example" { 21 | name = "example_user" 22 | database_id = data.mssql_database.example.id 23 | } 24 | 25 | data "mssql_schema" "example" { 26 | name = "example_schema" 27 | database_id = data.mssql_database.example.id 28 | } 29 | 30 | resource "mssql_schema_permission" "delete_to_example" { 31 | schema_id = data.mssql_schema.example.id 32 | principal_id = data.mssql_sql_user.example.id 33 | permission = "DELETE" 34 | } 35 | ``` 36 | 37 | 38 | ## Schema 39 | 40 | ### Required 41 | 42 | - `permission` (String) Name of schema SQL permission. For full list of supported permissions, see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-schema-permissions-transact-sql?view=azuresqldb-current#remarks) 43 | - `principal_id` (String) `/`. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal`. 44 | - `schema_id` (String) `/`. Can be retrieved using `mssql_schema`. 45 | 46 | ### Optional 47 | 48 | - `with_grant_option` (Boolean) When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals. Defaults to `false`. 49 | 50 | ### Read-Only 51 | 52 | - `id` (String) `///`. 53 | 54 | ## Import 55 | 56 | Import is supported using the following syntax: 57 | 58 | ```shell 59 | # import using /// - can be retrieved using `SELECT CONCAT(DB_ID(), '/', SCHEMA_ID(''), '/', DATABASE_PRINCIPAL_ID(''), '/DELETE')` 60 | terraform import mssql_schema_permission.example '7/5/8/DELETE' 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/resources/script.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_script Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Allows execution of arbitrary SQL scripts to check state and apply desired state. 7 | -> Note This resource is meant to be an escape hatch for all cases not supported by the provider's resources. Whenever possible, use dedicated resources, which offer better plan, validation and error reporting. 8 | --- 9 | 10 | # mssql_script (Resource) 11 | 12 | Allows execution of arbitrary SQL scripts to check state and apply desired state. 13 | 14 | -> **Note** This resource is meant to be an escape hatch for all cases not supported by the provider's resources. Whenever possible, use dedicated resources, which offer better plan, validation and error reporting. 15 | 16 | ## Example Usage 17 | 18 | ```terraform 19 | data "mssql_database" "test" { 20 | name = "test" 21 | } 22 | 23 | resource "mssql_script" "cdc" { 24 | database_id = data.mssql_database.test.id 25 | 26 | read_script = "SELECT COUNT(*) AS [is_enabled] FROM sys.change_tracking_databases WHERE database_id=${data.mssql_database.test.id}" 27 | delete_script = "ALTER DATABASE [${data.mssql_database.test.name}] SET CHANGE_TRACKING = OFF" 28 | 29 | update_script = < 41 | ## Schema 42 | 43 | ### Required 44 | 45 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. 46 | - `read_script` (String) SQL script returning current state of the DB. It must return single-row result set where column names match the keys of `state` map and all values are strings that will be compared against `state` to determine if the resource state matches DB state. 47 | - `state` (Map of String) Desired state of the DB. It is arbitrary map of string values that will be compared against the values returned by the `read_script`. 48 | - `update_script` (String) SQL script executed when the desired state specified in `state` attribute does not match the state returned by `read_script` 49 | 50 | ### Optional 51 | 52 | - `create_script` (String) SQL script executed when the resource does not exist in Terraform state. When not provided, `update_script` will be used to create the resource. 53 | - `delete_script` (String) SQL script executed when the resource is being destroyed. When not provided, no action will be taken during resource destruction. 54 | 55 | ### Read-Only 56 | 57 | - `id` (String) Used only internally by Terraform. Always set to `script` 58 | 59 | 60 | -------------------------------------------------------------------------------- /docs/resources/server_permission.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_server_permission Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Grants server-level permission. 7 | --- 8 | 9 | # mssql_server_permission (Resource) 10 | 11 | Grants server-level permission. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_sql_login" "example" { 17 | name = "example_login" 18 | } 19 | 20 | resource "mssql_server_permission" "connect_to_example" { 21 | principal_id = data.mssql_sql_login.example.principal_id 22 | permission = "CONNECT SQL" 23 | with_grant_option = true 24 | } 25 | ``` 26 | 27 | 28 | ## Schema 29 | 30 | ### Required 31 | 32 | - `permission` (String) Name of server-level SQL permission. For full list of supported permissions see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-server-permissions-transact-sql?view=azuresqldb-current#remarks) 33 | - `principal_id` (String) ID of the principal who will be granted `permission`. Can be retrieved using `mssql_server_role` or `mssql_sql_login`. 34 | 35 | ### Optional 36 | 37 | - `with_grant_option` (Boolean) When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals. Defaults to `false` 38 | 39 | ### Read-Only 40 | 41 | - `id` (String) `/` 42 | 43 | ## Import 44 | 45 | Import is supported using the following syntax: 46 | 47 | ```shell 48 | # import using / 49 | terraform import mssql_server_permission.example '7/CONNECT SQL' 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/resources/server_role.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_server_role Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Manages server-level role. 7 | --- 8 | 9 | # mssql_server_role (Resource) 10 | 11 | Manages server-level role. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | resource "mssql_server_role" "owner" { 17 | name = "owner_role" 18 | } 19 | 20 | resource "mssql_server_role" "example" { 21 | name = "example" 22 | owner_id = mssql_server_role.owner.id 23 | } 24 | ``` 25 | 26 | 27 | ## Schema 28 | 29 | ### Required 30 | 31 | - `name` (String) Role name. Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers) and cannot be longer than 128 chars. 32 | 33 | ### Optional 34 | 35 | - `owner_id` (String) ID of another server role or login owning this role. Can be retrieved using `mssql_server_role` or `mssql_sql_login`. 36 | 37 | ### Read-Only 38 | 39 | - `id` (String) Role principal ID. 40 | 41 | ## Import 42 | 43 | Import is supported using the following syntax: 44 | 45 | ```shell 46 | # import using - can be retrieved using `SELECT [principal_id] FROM sys.server_principals WHERE [name]=''` 47 | terraform import mssql_server_role.example 7 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/resources/server_role_member.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_server_role_member Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Manages server role membership. 7 | --- 8 | 9 | # mssql_server_role_member (Resource) 10 | 11 | Manages server role membership. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_sql_login" "member" { 17 | name = "member_login" 18 | } 19 | 20 | resource "mssql_server_role" "example" { 21 | name = "example" 22 | } 23 | 24 | resource "mssql_server_role_member" "example" { 25 | role_id = mssql_server_role.example.id 26 | member_id = data.mssql_sql_login.member.id 27 | } 28 | ``` 29 | 30 | 31 | ## Schema 32 | 33 | ### Required 34 | 35 | - `member_id` (String) ID of the member. Can be retrieved using `mssql_server_role` or `mssql_sql_login` 36 | - `role_id` (String) ID of the server role. Can be retrieved using `mssql_server_role` 37 | 38 | ### Read-Only 39 | 40 | - `id` (String) `/`. Role and member IDs can be retrieved using `mssql_server_role` or `mssql_sql_login` 41 | 42 | ## Import 43 | 44 | Import is supported using the following syntax: 45 | 46 | ```shell 47 | # import using / - can be retrieved using `sys.server_principals` view 48 | terraform import mssql_server_role_member.example '7/5' 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/resources/sql_user.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "mssql_sql_user Resource - terraform-provider-mssql" 4 | subcategory: "" 5 | description: |- 6 | Manages database-level user, based on SQL login. 7 | --- 8 | 9 | # mssql_sql_user (Resource) 10 | 11 | Manages database-level user, based on SQL login. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | data "mssql_database" "example" { 17 | name = "example" 18 | } 19 | 20 | resource "mssql_sql_login" "example" { 21 | name = "example" 22 | password = "Str0ngPa$$word12" 23 | must_change_password = true 24 | default_database_id = data.mssql_database.example.id 25 | default_language = "english" 26 | check_password_expiration = true 27 | check_password_policy = true 28 | } 29 | 30 | resource "mssql_sql_user" "example" { 31 | name = "example" 32 | database_id = data.mssql_database.example.id 33 | login_id = mssql_sql_login.example.id 34 | } 35 | 36 | output "user_id" { 37 | value = mssql_sql_user.example.id 38 | } 39 | ``` 40 | 41 | 42 | ## Schema 43 | 44 | ### Required 45 | 46 | - `login_id` (String) SID of SQL login. Can be retrieved using `mssql_sql_login` or `SELECT SUSER_SID('')`. 47 | - `name` (String) User name. Cannot be longer than 128 chars. 48 | 49 | ### Optional 50 | 51 | - `database_id` (String) ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`. Defaults to ID of `master`. 52 | 53 | ### Read-Only 54 | 55 | - `id` (String) `/`. User ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')`. 56 | 57 | ## Import 58 | 59 | Import is supported using the following syntax: 60 | 61 | ```shell 62 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', DATABASE_PRINCIPAL_ID(''))` 63 | terraform import mssql_sql_user.example '7/5' 64 | ``` 65 | -------------------------------------------------------------------------------- /examples/data-sources/mssql_azuread_service_principal/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_azuread_service_principal" "example" { 6 | name = "example" 7 | database_id = data.mssql_database.example.id 8 | } 9 | 10 | output "app_client_id" { 11 | value = data.mssql_azuread_service_principal.example.client_id 12 | } 13 | -------------------------------------------------------------------------------- /examples/data-sources/mssql_azuread_user/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_azuread_user" "example" { 6 | name = "example" 7 | database_id = data.mssql_database.example.id 8 | } 9 | 10 | output "user_object_id" { 11 | value = data.mssql_azuread_user.example.user_object_id 12 | } 13 | -------------------------------------------------------------------------------- /examples/data-sources/mssql_database/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | output "db_id" { 6 | value = data.mssql_database.example.id 7 | } 8 | 9 | output "db_collation" { 10 | value = data.mssql_database.example.collation 11 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_database_permissions/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_sql_user" "example" { 6 | name = "example_user" 7 | database_id = data.mssql_database.example.id 8 | } 9 | 10 | data "mssql_database_permissions" "example" { 11 | principal_id = data.mssql_sql_user.example.id 12 | } 13 | 14 | output "permissions" { 15 | value = data.mssql_database_permissions.example.permissions 16 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_database_role/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "master" { 2 | name = "master" 3 | } 4 | 5 | data "mssql_database_role" "example" { 6 | name = "public" 7 | database_id = data.mssql_database.master.id 8 | } 9 | 10 | output "id" { 11 | value = data.mssql_database_role.example.id 12 | } 13 | -------------------------------------------------------------------------------- /examples/data-sources/mssql_database_roles/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "master" { 2 | name = "master" 3 | } 4 | 5 | data "mssql_database_roles" "example" { 6 | database_id = data.mssql_database.master.id 7 | } 8 | 9 | output "roles" { 10 | value = data.mssql_database_roles.example.roles 11 | } 12 | -------------------------------------------------------------------------------- /examples/data-sources/mssql_databases/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_databases" "example" {} 2 | 3 | output "databases" { 4 | value = data.mssql_databases.example.databases 5 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_query/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "test" { 2 | name = "test" 3 | } 4 | 5 | data "mssql_query" "column" { 6 | database_id = data.mssql_database.test.id 7 | query = "SELECT [column_id], [name] FROM sys.columns WHERE [object_id] = OBJECT_ID('test_table')" 8 | } 9 | 10 | output "column_names" { 11 | value = data.mssql_query.column.result[*].name 12 | } 13 | 14 | -------------------------------------------------------------------------------- /examples/data-sources/mssql_schema/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_schema" "by_name" { 6 | database_id = data.mssql_database.example.id 7 | name = "dbo" 8 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_schema_permissions/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_sql_user" "example" { 6 | name = "example_user" 7 | database_id = data.mssql_database.example.id 8 | } 9 | 10 | data "mssql_schema" "example" { 11 | name = "example_schema" 12 | database_id = data.mssql_database.example.id 13 | } 14 | 15 | data "mssql_schema_permissions" "example" { 16 | schema_id = data.mssql_schema.example.id 17 | principal_id = data.mssql_sql_user.example.id 18 | } 19 | 20 | output "permissions" { 21 | value = data.mssql_schema_permissions.example.permissions 22 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_schemas/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_schemas" "all" { 6 | database_id = data.mssql_database.example.id 7 | } 8 | 9 | output "all_schema_names" { 10 | value = data.mssql_schemas.all.schemas[*].name 11 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_server_permissions/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_sql_login" "example" { 2 | name = "example_login" 3 | } 4 | 5 | data "mssql_server_permissions" "example" { 6 | principal_id = data.mssql_sql_login.example.principal_id 7 | } 8 | 9 | output "permissions" { 10 | value = data.mssql_server_permissions.example.permissions 11 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_server_role/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_server_role" "by_name" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_server_role" "by_id" { 6 | id = 8 7 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_server_roles/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_server_roles" "all" {} 2 | 3 | output "roles" { 4 | value = data.mssql_server_roles.all.roles 5 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_sql_login/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_sql_login" "sa" { 2 | name = "sa" 3 | } 4 | 5 | output "id" { 6 | value = data.mssql_sql_login.sa.id 7 | } 8 | 9 | output "db_id" { 10 | value = data.mssql_sql_login.sa.default_database_id 11 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_sql_logins/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_sql_logins" "example" {} 2 | 3 | output "databases" { 4 | value = data.mssql_sql_logins.example.logins 5 | } -------------------------------------------------------------------------------- /examples/data-sources/mssql_sql_user/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "master" { 2 | name = "master" 3 | } 4 | 5 | data "mssql_sql_user" "example" { 6 | name = "dbo" 7 | database_id = data.mssql_database.master.id 8 | } 9 | 10 | output "id" { 11 | value = data.mssql_sql_user.example.id 12 | } 13 | -------------------------------------------------------------------------------- /examples/data-sources/mssql_sql_users/data-source.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "master" { 2 | name = "master" 3 | } 4 | 5 | data "mssql_sql_users" "example" { 6 | database_id = data.mssql_database.master.id 7 | } 8 | 9 | output "users" { 10 | value = data.mssql_sql_users.example.users 11 | } 12 | -------------------------------------------------------------------------------- /examples/provider/aad_default.tf: -------------------------------------------------------------------------------- 1 | provider "mssql" { 2 | hostname = "example.database.windows.net" 3 | port = 1433 4 | azure_auth = {} 5 | } 6 | -------------------------------------------------------------------------------- /examples/provider/aad_sp.tf: -------------------------------------------------------------------------------- 1 | provider "mssql" { 2 | hostname = "example.database.windows.net" 3 | port = 1433 4 | 5 | azure_auth = { 6 | client_id = "94e8d55d-cbbc-4e41-b21a-8923d83f9a85" 7 | client_secret = "client_secret" 8 | tenant_id = "a352c914-bfd9-4b7e-8b1d-554a58353f22" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/provider/azure_sql.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | publicip = { 4 | source = "nxt-engineering/publicip" 5 | } 6 | 7 | mssql = { 8 | source = "PGSSoft/mssql" 9 | } 10 | } 11 | } 12 | 13 | provider "azurerm" { 14 | features {} 15 | } 16 | 17 | provider "mssql" { 18 | hostname = azurerm_mssql_server.this.fully_qualified_domain_name 19 | azure_auth = {} 20 | } 21 | 22 | data "azurerm_client_config" "current" {} 23 | 24 | data "publicip_address" "default" { 25 | source_ip = "0.0.0.0" 26 | } 27 | 28 | resource "azurerm_resource_group" "azure_test" { 29 | name = "terraform-mssql-test-local" 30 | location = "WestEurope" 31 | } 32 | 33 | resource "azurerm_mssql_server" "this" { 34 | name = "terraform-mssql-test-local" 35 | resource_group_name = azurerm_resource_group.azure_test.name 36 | location = azurerm_resource_group.azure_test.location 37 | version = "12.0" 38 | 39 | azuread_administrator { 40 | login_username = data.azurerm_client_config.current.object_id 41 | object_id = data.azurerm_client_config.current.object_id 42 | azuread_authentication_only = true 43 | } 44 | } 45 | 46 | resource "azurerm_mssql_firewall_rule" "caller" { 47 | name = "caller" 48 | server_id = azurerm_mssql_server.this.id 49 | start_ip_address = data.publicip_address.default.ip 50 | end_ip_address = data.publicip_address.default.ip 51 | } 52 | 53 | resource "azurerm_mssql_database" "test" { 54 | name = "test" 55 | server_id = azurerm_mssql_server.this.id 56 | } 57 | 58 | data "mssql_database" "test" { 59 | name = azurerm_mssql_database.test.name 60 | } 61 | 62 | data "mssql_database_role" "test_owner" { 63 | database_id = data.mssql_database.test.id 64 | name = "db_owner" 65 | } 66 | 67 | resource "mssql_azuread_user" "caller" { 68 | name = data.azurerm_client_config.current.object_id 69 | database_id = data.mssql_database.test.id 70 | user_object_id = data.azurerm_client_config.current.object_id 71 | } 72 | 73 | resource "mssql_database_role_member" "caller_owner" { 74 | role_id = data.mssql_database_role.test_owner.id 75 | member_id = mssql_azuread_user.caller.id 76 | } -------------------------------------------------------------------------------- /examples/provider/sql.tf: -------------------------------------------------------------------------------- 1 | provider "mssql" { 2 | hostname = "localhost" 3 | port = 1433 4 | 5 | sql_auth = { 6 | username = "sa" 7 | password = "sa_password" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/resources/mssql_azuread_service_principal/import.sh: -------------------------------------------------------------------------------- 1 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', principal_id) FROM sys.database_principals WHERE [name] = ''` 2 | terraform import mssql_azuread_service_principal.example '7/5' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_azuread_service_principal/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "azuread_service_principal" "example" { 6 | display_name = "test-application" 7 | } 8 | 9 | resource "mssql_azuread_service_principal" "example" { 10 | name = "example" 11 | database_id = data.mssql_database.example.id 12 | client_id = data.azuread_service_principal.example.application_id 13 | } 14 | 15 | output "user_id" { 16 | value = mssql_azuread_service_principal.example.id 17 | } 18 | -------------------------------------------------------------------------------- /examples/resources/mssql_azuread_user/import.sh: -------------------------------------------------------------------------------- 1 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', principal_id) FROM sys.database_principals WHERE [name] = ''` 2 | terraform import mssql_azuread_user.example '7/5' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_azuread_user/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "azuread_user" "example" { 6 | user_principal_name = "user@example.com" 7 | } 8 | 9 | resource "mssql_azuread_user" "example" { 10 | name = "example" 11 | database_id = data.mssql_database.example.id 12 | user_object_id = data.azuread_user.example.object_id 13 | } 14 | 15 | output "user_id" { 16 | value = mssql_azuread_user.example.id 17 | } -------------------------------------------------------------------------------- /examples/resources/mssql_database/import.sh: -------------------------------------------------------------------------------- 1 | # import using database ID - can be retrieved using `SELECT DB_ID('')` 2 | terraform import mssql_database.example 12 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_database/resource.tf: -------------------------------------------------------------------------------- 1 | resource "mssql_database" "example" { 2 | name = "example" 3 | collation = "SQL_Latin1_General_CP1_CS_AS" 4 | } -------------------------------------------------------------------------------- /examples/resources/mssql_database_permission/import.sh: -------------------------------------------------------------------------------- 1 | # import using // - can be retrieved using `SELECT CONCAT(DB_ID(), '/', DATABASE_PRINCIPAL_ID(''), '/DELETE')` 2 | terraform import mssql_database_permission.example '7/5/DELETE' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_database_permission/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_sql_user" "example" { 6 | name = "example_user" 7 | database_id = data.mssql_database.example.id 8 | } 9 | 10 | resource "mssql_database_permission" "delete_to_example" { 11 | principal_id = data.mssql_sql_user.example.id 12 | permission = "DELETE" 13 | } -------------------------------------------------------------------------------- /examples/resources/mssql_database_role/import.sh: -------------------------------------------------------------------------------- 1 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', DATABASE_PRINCIPAL_ID(''))` 2 | terraform import mssql_database_role.example '7/5' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_database_role/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_sql_user" "owner" { 6 | name = "example_user" 7 | } 8 | 9 | resource "mssql_database_role" "example" { 10 | name = "example" 11 | database_id = data.mssql_database.example.id 12 | owner_id = data.mssql_sql_user.owner.id 13 | } -------------------------------------------------------------------------------- /examples/resources/mssql_database_role_member/import.sh: -------------------------------------------------------------------------------- 1 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', DATABASE_PRINCIPAL_ID(''), '/', DATABASE_PRINCIPAL_ID('/ - can be retrieved using `SELECT CONCAT(DB_ID(), '/', SCHEMA_ID(''))` 2 | terraform import mssql_schema.example '7/5' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_schema/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_sql_user" "owner" { 6 | name = "example_user" 7 | } 8 | 9 | resource "mssql_schema" "example" { 10 | name = "example" 11 | database_id = data.mssql_database.example.id 12 | owner_id = data.mssql_sql_user.owner.id 13 | } -------------------------------------------------------------------------------- /examples/resources/mssql_schema_permission/import.sh: -------------------------------------------------------------------------------- 1 | # import using /// - can be retrieved using `SELECT CONCAT(DB_ID(), '/', SCHEMA_ID(''), '/', DATABASE_PRINCIPAL_ID(''), '/DELETE')` 2 | terraform import mssql_schema_permission.example '7/5/8/DELETE' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_schema_permission/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | data "mssql_sql_user" "example" { 6 | name = "example_user" 7 | database_id = data.mssql_database.example.id 8 | } 9 | 10 | data "mssql_schema" "example" { 11 | name = "example_schema" 12 | database_id = data.mssql_database.example.id 13 | } 14 | 15 | resource "mssql_schema_permission" "delete_to_example" { 16 | schema_id = data.mssql_schema.example.id 17 | principal_id = data.mssql_sql_user.example.id 18 | permission = "DELETE" 19 | } -------------------------------------------------------------------------------- /examples/resources/mssql_script/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "test" { 2 | name = "test" 3 | } 4 | 5 | resource "mssql_script" "cdc" { 6 | database_id = data.mssql_database.test.id 7 | 8 | read_script = "SELECT COUNT(*) AS [is_enabled] FROM sys.change_tracking_databases WHERE database_id=${data.mssql_database.test.id}" 9 | delete_script = "ALTER DATABASE [${data.mssql_database.test.name}] SET CHANGE_TRACKING = OFF" 10 | 11 | update_script = </ 2 | terraform import mssql_server_permission.example '7/CONNECT SQL' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_server_permission/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_sql_login" "example" { 2 | name = "example_login" 3 | } 4 | 5 | resource "mssql_server_permission" "connect_to_example" { 6 | principal_id = data.mssql_sql_login.example.principal_id 7 | permission = "CONNECT SQL" 8 | with_grant_option = true 9 | } -------------------------------------------------------------------------------- /examples/resources/mssql_server_role/import.sh: -------------------------------------------------------------------------------- 1 | # import using - can be retrieved using `SELECT [principal_id] FROM sys.server_principals WHERE [name]=''` 2 | terraform import mssql_server_role.example 7 -------------------------------------------------------------------------------- /examples/resources/mssql_server_role/resource.tf: -------------------------------------------------------------------------------- 1 | resource "mssql_server_role" "owner" { 2 | name = "owner_role" 3 | } 4 | 5 | resource "mssql_server_role" "example" { 6 | name = "example" 7 | owner_id = mssql_server_role.owner.id 8 | } -------------------------------------------------------------------------------- /examples/resources/mssql_server_role_member/import.sh: -------------------------------------------------------------------------------- 1 | # import using / - can be retrieved using `sys.server_principals` view 2 | terraform import mssql_server_role_member.example '7/5' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_server_role_member/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_sql_login" "member" { 2 | name = "member_login" 3 | } 4 | 5 | resource "mssql_server_role" "example" { 6 | name = "example" 7 | } 8 | 9 | resource "mssql_server_role_member" "example" { 10 | role_id = mssql_server_role.example.id 11 | member_id = data.mssql_sql_login.member.id 12 | } -------------------------------------------------------------------------------- /examples/resources/mssql_sql_login/import.sh: -------------------------------------------------------------------------------- 1 | # import using login ID - can be retrieved using `SELECT SUSER_SID('')` 2 | terraform import mssql_sql_login.example 0x27578D8516843E4094EFA2CEED085C82 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_sql_login/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | resource "mssql_sql_login" "example" { 6 | name = "example" 7 | password = "Str0ngPa$$word12" 8 | must_change_password = true 9 | default_database_id = data.mssql_database.example.id 10 | default_language = "english" 11 | check_password_expiration = true 12 | check_password_policy = true 13 | } 14 | 15 | output "login_id" { 16 | value = mssql_sql_login.example.id 17 | } -------------------------------------------------------------------------------- /examples/resources/mssql_sql_user/import.sh: -------------------------------------------------------------------------------- 1 | # import using / - can be retrieved using `SELECT CONCAT(DB_ID(), '/', DATABASE_PRINCIPAL_ID(''))` 2 | terraform import mssql_sql_user.example '7/5' 3 | -------------------------------------------------------------------------------- /examples/resources/mssql_sql_user/resource.tf: -------------------------------------------------------------------------------- 1 | data "mssql_database" "example" { 2 | name = "example" 3 | } 4 | 5 | resource "mssql_sql_login" "example" { 6 | name = "example" 7 | password = "Str0ngPa$$word12" 8 | must_change_password = true 9 | default_database_id = data.mssql_database.example.id 10 | default_language = "english" 11 | check_password_expiration = true 12 | check_password_policy = true 13 | } 14 | 15 | resource "mssql_sql_user" "example" { 16 | name = "example" 17 | database_id = data.mssql_database.example.id 18 | login_id = mssql_sql_login.example.id 19 | } 20 | 21 | output "user_id" { 22 | value = mssql_sql_user.example.id 23 | } -------------------------------------------------------------------------------- /internal/core/attrs/numericId.go: -------------------------------------------------------------------------------- 1 | package attrs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 7 | "github.com/hashicorp/terraform-plugin-framework/types/basetypes" 8 | ) 9 | 10 | func NumericIdType[T sql.NumericObjectId]() basetypes.StringTypable { 11 | var t basetypes.StringTypable 12 | t = numericIdType[T]{ 13 | compositeIdType{ 14 | elemCount: 1, 15 | valueFactory: func(id CompositeId) basetypes.StringValuable { 16 | id.attrType = &t 17 | return NumericId[T]{id} 18 | }, 19 | }, 20 | } 21 | return t 22 | } 23 | 24 | type numericIdType[T sql.NumericObjectId] struct { 25 | compositeIdType 26 | } 27 | 28 | func NumericIdValue[T sql.NumericObjectId](id T) NumericId[T] { 29 | t := NumericIdType[T]() 30 | 31 | return NumericId[T]{ 32 | CompositeId{ 33 | attrType: &t, 34 | elems: []string{fmt.Sprint(id)}, 35 | }, 36 | } 37 | } 38 | 39 | type NumericId[T sql.NumericObjectId] struct { 40 | CompositeId 41 | } 42 | 43 | func (id NumericId[T]) Id(ctx context.Context) T { 44 | return T(id.GetInt(ctx, 0)) 45 | } 46 | -------------------------------------------------------------------------------- /internal/core/attrs/permissionId.go: -------------------------------------------------------------------------------- 1 | package attrs 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 7 | "github.com/hashicorp/terraform-plugin-framework/types/basetypes" 8 | ) 9 | 10 | func PermissionIdType[T sql.NumericObjectId]() basetypes.StringTypable { 11 | var t basetypes.StringTypable 12 | t = permissionIdType[T]{ 13 | compositeIdType{ 14 | elemCount: 2, 15 | valueFactory: func(id CompositeId) basetypes.StringValuable { 16 | id.attrType = &t 17 | return PermissionId[T]{id} 18 | }, 19 | }, 20 | } 21 | return t 22 | } 23 | 24 | type permissionIdType[T sql.NumericObjectId] struct { 25 | compositeIdType 26 | } 27 | 28 | func PermissionIdValue[T sql.NumericObjectId](id T, permission string) PermissionId[T] { 29 | t := PermissionIdType[T]() 30 | 31 | return PermissionId[T]{ 32 | CompositeId{ 33 | attrType: &t, 34 | elems: []string{fmt.Sprint(id), permission}, 35 | }, 36 | } 37 | } 38 | 39 | type PermissionId[T sql.NumericObjectId] struct { 40 | CompositeId 41 | } 42 | 43 | func (id PermissionId[T]) ObjectId(ctx context.Context) T { 44 | return T(id.GetInt(ctx, 0)) 45 | } 46 | 47 | func (id PermissionId[T]) Permission() string { 48 | return id.GetString(1) 49 | } 50 | -------------------------------------------------------------------------------- /internal/core/datasource/datasource.go: -------------------------------------------------------------------------------- 1 | package datasource 2 | 3 | import ( 4 | "context" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 7 | "github.com/hashicorp/terraform-plugin-framework/datasource/schema" 8 | ) 9 | 10 | type StateSetter[TData any] func(state TData) 11 | 12 | type ConfigureRequest struct { 13 | Conn sql.Connection 14 | } 15 | 16 | var _ utils.ErrorMonad = MonadRequest{} 17 | 18 | type MonadRequest struct { 19 | monad utils.ErrorMonad 20 | } 21 | 22 | func (r MonadRequest) Then(f func()) utils.ErrorMonad { 23 | return r.monad.Then(f) 24 | } 25 | 26 | type SchemaRequest struct{} 27 | 28 | type SchemaResponse struct { 29 | Schema schema.Schema 30 | } 31 | 32 | type ReadRequest[TData any] struct { 33 | MonadRequest 34 | Conn sql.Connection 35 | Config TData 36 | } 37 | 38 | type ReadResponse[TData any] struct { 39 | state TData 40 | exists bool 41 | } 42 | 43 | func (r *ReadResponse[TData]) SetState(state TData) { 44 | r.state = state 45 | r.exists = true 46 | } 47 | 48 | type DataSource[TData any] interface { 49 | GetName() string 50 | Schema(ctx context.Context, req SchemaRequest, resp *SchemaResponse) 51 | Read(ctx context.Context, req ReadRequest[TData], resp *ReadResponse[TData]) 52 | } 53 | 54 | type ValidateRequest[TData any] struct { 55 | MonadRequest 56 | Config TData 57 | } 58 | 59 | type ValidateResponse[TData any] struct{} 60 | 61 | type DataSourceWithValidation[TData any] interface { 62 | Validate(ctx context.Context, req ValidateRequest[TData], resp *ValidateResponse[TData]) 63 | } 64 | -------------------------------------------------------------------------------- /internal/core/resource/resource.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "context" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 7 | "github.com/hashicorp/terraform-plugin-framework/resource/schema" 8 | ) 9 | 10 | type StateSetter[TData any] func(state TData) 11 | 12 | var _ utils.ErrorMonad = monadRequest{} 13 | 14 | type monadRequest struct { 15 | monad utils.ErrorMonad 16 | } 17 | 18 | type requestBase struct { 19 | monadRequest 20 | Conn sql.Connection 21 | } 22 | 23 | func (r monadRequest) Then(f func()) utils.ErrorMonad { 24 | return r.monad.Then(f) 25 | } 26 | 27 | type stateResponse[TData any] struct { 28 | State TData 29 | } 30 | 31 | type ConfigureRequest struct { 32 | Conn sql.Connection 33 | } 34 | 35 | type ReadRequest[TData any] struct { 36 | requestBase 37 | State TData 38 | } 39 | 40 | type ReadResponse[TData any] struct { 41 | state TData 42 | exists bool 43 | } 44 | 45 | func (r *ReadResponse[TData]) SetState(state TData) { 46 | r.state = state 47 | r.exists = true 48 | } 49 | 50 | type CreateRequest[TData any] struct { 51 | requestBase 52 | Plan TData 53 | } 54 | 55 | type CreateResponse[TData any] stateResponse[TData] 56 | 57 | type UpdateRequest[TData any] struct { 58 | requestBase 59 | Plan TData 60 | State TData 61 | } 62 | 63 | type UpdateResponse[TData any] stateResponse[TData] 64 | 65 | type DeleteRequest[TData any] struct { 66 | requestBase 67 | State TData 68 | } 69 | 70 | type DeleteResponse[TData any] struct{} 71 | 72 | type SchemaRequest struct{} 73 | 74 | type SchemaResponse struct { 75 | Schema schema.Schema 76 | } 77 | 78 | type Resource[TData any] interface { 79 | GetName() string 80 | Schema(ctx context.Context, req SchemaRequest, resp *SchemaResponse) 81 | Read(ctx context.Context, req ReadRequest[TData], resp *ReadResponse[TData]) 82 | Create(ctx context.Context, req CreateRequest[TData], resp *CreateResponse[TData]) 83 | Update(ctx context.Context, req UpdateRequest[TData], resp *UpdateResponse[TData]) 84 | Delete(ctx context.Context, req DeleteRequest[TData], resp *DeleteResponse[TData]) 85 | } 86 | 87 | type ValidateRequest[TData any] struct { 88 | monadRequest 89 | Config TData 90 | } 91 | 92 | type ValidateResponse[TData any] struct{} 93 | 94 | type ResourceWithValidation[TData any] interface { 95 | Validate(ctx context.Context, req ValidateRequest[TData], resp *ValidateResponse[TData]) 96 | } 97 | -------------------------------------------------------------------------------- /internal/core/resourceContext.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "context" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 6 | ) 7 | 8 | type ResourceContext struct { 9 | ConnFactory func(ctx context.Context) sql.Connection 10 | } 11 | -------------------------------------------------------------------------------- /internal/core/service.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 5 | "github.com/hashicorp/terraform-plugin-framework/datasource" 6 | "github.com/hashicorp/terraform-plugin-framework/resource" 7 | ) 8 | 9 | type AccTest func(testCtx *acctest.TestContext) 10 | 11 | type AccTests struct { 12 | Resource AccTest 13 | DataSource AccTest 14 | ListDataSource AccTest 15 | } 16 | 17 | type Service interface { 18 | Name() string 19 | Resources() []func() resource.ResourceWithConfigure 20 | DataSources() []func() datasource.DataSourceWithConfigure 21 | Tests() AccTests 22 | } 23 | -------------------------------------------------------------------------------- /internal/planModifiers/ignoreCaseModifier.go: -------------------------------------------------------------------------------- 1 | package planModifiers 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-framework/attr" 6 | "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" 7 | "strings" 8 | ) 9 | 10 | func IgnoreCase() planmodifier.String { 11 | return ignoreCaseModifier{} 12 | } 13 | 14 | type ignoreCaseModifier struct{} 15 | 16 | func (m ignoreCaseModifier) Description(context.Context) string { 17 | return "When config and state string values only differ in casing, the value from state will be used in plan" 18 | } 19 | 20 | func (m ignoreCaseModifier) MarkdownDescription(ctx context.Context) string { 21 | return m.Description(ctx) 22 | } 23 | 24 | func (m ignoreCaseModifier) PlanModifyString(ctx context.Context, request planmodifier.StringRequest, response *planmodifier.StringResponse) { 25 | isNotSet := func(v attr.Value) bool { 26 | return v == nil || v.IsNull() || v.IsUnknown() 27 | } 28 | 29 | if isNotSet(request.StateValue) || isNotSet(request.PlanValue) { 30 | return 31 | } 32 | 33 | if strings.ToUpper(request.PlanValue.ValueString()) != strings.ToUpper(request.StateValue.ValueString()) { 34 | return 35 | } 36 | 37 | response.PlanValue = request.StateValue 38 | } 39 | -------------------------------------------------------------------------------- /internal/planModifiers/ignoreCaseModifier_test.go: -------------------------------------------------------------------------------- 1 | package planModifiers 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" 6 | "github.com/hashicorp/terraform-plugin-framework/types" 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestIgnoreCaseModifier(t *testing.T) { 12 | cases := map[string]struct { 13 | request planmodifier.StringRequest 14 | expectedValue types.String 15 | }{ 16 | "empty state": { 17 | request: planmodifier.StringRequest{ 18 | StateValue: types.StringUnknown(), 19 | PlanValue: types.StringValue("plannedValue"), 20 | }, 21 | expectedValue: types.StringValue("plannedValue"), 22 | }, 23 | "empty plan": { 24 | request: planmodifier.StringRequest{ 25 | StateValue: types.StringValue("stateValue"), 26 | PlanValue: types.StringNull(), 27 | }, 28 | expectedValue: types.StringNull(), 29 | }, 30 | "matching case": { 31 | request: planmodifier.StringRequest{ 32 | StateValue: types.StringValue("matchingCase"), 33 | PlanValue: types.StringValue("matchingCase"), 34 | }, 35 | expectedValue: types.StringValue("matchingCase"), 36 | }, 37 | "not matching case": { 38 | request: planmodifier.StringRequest{ 39 | StateValue: types.StringValue("NotMatchingCase"), 40 | PlanValue: types.StringValue("NOTMATCHINGCASE"), 41 | }, 42 | expectedValue: types.StringValue("NotMatchingCase"), 43 | }, 44 | } 45 | 46 | for name, tc := range cases { 47 | t.Run(name, func(t *testing.T) { 48 | modifier := IgnoreCase() 49 | response := planmodifier.StringResponse{PlanValue: tc.request.PlanValue} 50 | 51 | modifier.PlanModifyString(context.Background(), tc.request, &response) 52 | 53 | assert.Equal(t, tc.expectedValue, response.PlanValue) 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /internal/provider/services.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/azureADServicePrincipal" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/azureADUser" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/database" 8 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/databasePermission" 9 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/databaseRole" 10 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/databaseRoleMember" 11 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/schema" 12 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/schemaPermission" 13 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/script" 14 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/serverPermission" 15 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/serverRole" 16 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/serverRoleMember" 17 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/sqlLogin" 18 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/sqlUser" 19 | ) 20 | 21 | func Services() []core.Service { 22 | return []core.Service{ 23 | azureADServicePrincipal.Service(), 24 | azureADUser.Service(), 25 | 26 | database.Service(), 27 | databasePermission.Service(), 28 | databaseRole.Service(), 29 | databaseRoleMember.Service(), 30 | sqlLogin.Service(), 31 | sqlUser.Service(), 32 | schema.Service(), 33 | schemaPermission.Service(), 34 | serverRole.Service(), 35 | serverRoleMember.Service(), 36 | serverPermission.Service(), 37 | 38 | script.Service(), 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/provider/services_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 6 | "github.com/hashicorp/terraform-plugin-framework/provider" 7 | "testing" 8 | ) 9 | 10 | func TestServices(t *testing.T) { 11 | ctx := acctest.NewContext(t, func(connection sql.Connection) provider.Provider { 12 | return &mssqlProvider{ 13 | Version: VersionTest, 14 | Db: connection, 15 | } 16 | }) 17 | 18 | defer ctx.Cleanup() 19 | 20 | for _, svc := range Services() { 21 | ctx.Run(svc.Name(), func(svcCtx *acctest.TestContext) { 22 | if test := svc.Tests().Resource; test != nil { 23 | svcCtx.Run("resource", test) 24 | } 25 | 26 | if test := svc.Tests().DataSource; test != nil { 27 | svcCtx.Run("data_source", test) 28 | } 29 | 30 | if test := svc.Tests().ListDataSource; test != nil { 31 | svcCtx.Run("list", test) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/services/azureADServicePrincipal/base.go: -------------------------------------------------------------------------------- 1 | package azureADServicePrincipal 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 9 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 10 | "github.com/hashicorp/terraform-plugin-framework/types" 11 | ) 12 | 13 | var attrDescriptions = map[string]string{ 14 | "id": "`/`. User ID can be retrieved using `sys.database_principals` view.", 15 | "name": "User name. Cannot be longer than 128 chars.", 16 | "client_id": "Azure AD client_id of the Service Principal. This can be either regular Service Principal or Managed Service Identity.", 17 | } 18 | 19 | type resourceData struct { 20 | Id types.String `tfsdk:"id"` 21 | Name types.String `tfsdk:"name"` 22 | DatabaseId types.String `tfsdk:"database_id"` 23 | ClientId types.String `tfsdk:"client_id"` 24 | } 25 | 26 | func (d resourceData) toSettings() sql.UserSettings { 27 | return sql.UserSettings{ 28 | Name: d.Name.ValueString(), 29 | AADObjectId: sql.AADObjectId(d.ClientId.ValueString()), 30 | Type: sql.USER_TYPE_AZUREAD, 31 | } 32 | } 33 | 34 | func (d resourceData) withSettings(ctx context.Context, settings sql.UserSettings) resourceData { 35 | if settings.Type != sql.USER_TYPE_AZUREAD { 36 | utils.AddError(ctx, "Invalid user type", fmt.Errorf("expected user type %d, but got %d", sql.USER_TYPE_AZUREAD, settings.Type)) 37 | return d 38 | } 39 | 40 | d.Name = types.StringValue(settings.Name) 41 | d.ClientId = types.StringValue(strings.ToUpper(fmt.Sprint(settings.AADObjectId))) 42 | return d 43 | } 44 | -------------------------------------------------------------------------------- /internal/services/azureADServicePrincipal/main.go: -------------------------------------------------------------------------------- 1 | package azureADServicePrincipal 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkResource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "azuread_service_principal" 19 | } 20 | 21 | func (s service) Resources() []func() sdkResource.ResourceWithConfigure { 22 | return []func() sdkResource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[resourceData](&dataSource{}), 30 | } 31 | } 32 | 33 | func (s service) Tests() core.AccTests { 34 | return core.AccTests{ 35 | Resource: testResource, 36 | DataSource: testDataSource, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/services/azureADServicePrincipal/resource_acctest.go: -------------------------------------------------------------------------------- 1 | package azureADServicePrincipal 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 9 | "strings" 10 | ) 11 | 12 | func testResource(testCtx *acctest.TestContext) { 13 | if !testCtx.IsAzureTest { 14 | return 15 | } 16 | 17 | var userId int 18 | var userResourceId string 19 | 20 | newResource := func(resourceName string, name string) string { 21 | return fmt.Sprintf(` 22 | resource "mssql_azuread_service_principal" %[1]q { 23 | name = %[2]q 24 | database_id = %[4]d 25 | client_id = %[3]q 26 | } 27 | `, resourceName, name, testCtx.AzureTestMSI.ClientId, testCtx.DefaultDBId) 28 | } 29 | 30 | testCtx.Test(resource.TestCase{ 31 | Steps: []resource.TestStep{ 32 | { 33 | Config: newResource("test_user", "test_aad_user"), 34 | Check: resource.ComposeTestCheckFunc( 35 | testCtx.SqlCheckDefaultDB(func(db *sql.DB) error { 36 | if err := db.QueryRow("SELECT principal_id FROM sys.database_principals WHERE [name] = 'test_aad_user'").Scan(&userId); err != nil { 37 | return err 38 | } 39 | 40 | userResourceId = fmt.Sprintf("%d/%d", testCtx.DefaultDBId, userId) 41 | 42 | return nil 43 | }), 44 | resource.ComposeAggregateTestCheckFunc( 45 | resource.TestCheckResourceAttrPtr("mssql_azuread_service_principal.test_user", "id", &userResourceId), 46 | testCtx.SqlCheckDefaultDB(func(db *sql.DB) error { 47 | var userType, userSid string 48 | err := db.QueryRow("SELECT [type], CONVERT(VARCHAR(36), CONVERT(UNIQUEIDENTIFIER, [sid], 1), 1) FROM sys.database_principals WHERE principal_id = @p1", userId). 49 | Scan(&userType, &userSid) 50 | 51 | testCtx.Assert.Equal("E", strings.ToUpper(userType), "user type") 52 | testCtx.Assert.Equal(strings.ToUpper(testCtx.AzureTestMSI.ClientId), strings.ToUpper(userSid), "user SID") 53 | 54 | return err 55 | }), 56 | ), 57 | ), 58 | }, 59 | { 60 | ResourceName: "mssql_azuread_service_principal.test_user", 61 | ImportState: true, 62 | ImportStateIdFunc: func(*terraform.State) (string, error) { 63 | return userResourceId, nil 64 | }, 65 | ImportStateCheck: func(states []*terraform.InstanceState) error { 66 | for _, state := range states { 67 | if state.ID == userResourceId { 68 | testCtx.Assert.Equal("test_aad_user", state.Attributes["name"]) 69 | testCtx.Assert.Equal(fmt.Sprint(testCtx.DefaultDBId), state.Attributes["database_id"]) 70 | testCtx.Assert.Equal(strings.ToUpper(testCtx.AzureTestMSI.ClientId), strings.ToUpper(state.Attributes["client_id"])) 71 | } 72 | } 73 | 74 | return nil 75 | }, 76 | }, 77 | }, 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /internal/services/azureADUser/base.go: -------------------------------------------------------------------------------- 1 | package azureADUser 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 8 | "github.com/hashicorp/terraform-plugin-framework/types" 9 | "strings" 10 | ) 11 | 12 | var attrDescriptions = map[string]string{ 13 | "id": "`/`. User ID can be retrieved using `sys.database_principals` view.", 14 | "name": "User name. Cannot be longer than 128 chars.", 15 | "user_object_id": "Azure AD object_id of the user. This can be either regular user or a group.", 16 | } 17 | 18 | type resourceData struct { 19 | Id types.String `tfsdk:"id"` 20 | Name types.String `tfsdk:"name"` 21 | DatabaseId types.String `tfsdk:"database_id"` 22 | UserObjectId types.String `tfsdk:"user_object_id"` 23 | } 24 | 25 | func (d resourceData) toSettings() sql.UserSettings { 26 | return sql.UserSettings{ 27 | Name: d.Name.ValueString(), 28 | AADObjectId: sql.AADObjectId(d.UserObjectId.ValueString()), 29 | Type: sql.USER_TYPE_AZUREAD, 30 | } 31 | } 32 | 33 | func (d resourceData) withSettings(ctx context.Context, settings sql.UserSettings) resourceData { 34 | if settings.Type != sql.USER_TYPE_AZUREAD { 35 | utils.AddError(ctx, "Invalid user type", fmt.Errorf("expected user type %d, but got %d", sql.USER_TYPE_AZUREAD, settings.Type)) 36 | return d 37 | } 38 | 39 | d.Name = types.StringValue(settings.Name) 40 | d.UserObjectId = types.StringValue(strings.ToUpper(fmt.Sprint(settings.AADObjectId))) 41 | return d 42 | } 43 | -------------------------------------------------------------------------------- /internal/services/azureADUser/main.go: -------------------------------------------------------------------------------- 1 | package azureADUser 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkResource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "azuread_user" 19 | } 20 | 21 | func (s service) Resources() []func() sdkResource.ResourceWithConfigure { 22 | return []func() sdkResource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[resourceData](&dataSource{}), 30 | } 31 | } 32 | 33 | func (s service) Tests() core.AccTests { 34 | return core.AccTests{ 35 | Resource: testResource, 36 | DataSource: testDataSource, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/services/azureADUser/resource_acctest.go: -------------------------------------------------------------------------------- 1 | package azureADUser 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 9 | "strings" 10 | ) 11 | 12 | func testResource(testCtx *acctest.TestContext) { 13 | if !testCtx.IsAzureTest { 14 | return 15 | } 16 | 17 | var userId int 18 | var userResourceId string 19 | 20 | newResource := func(resourceName string, name string) string { 21 | return fmt.Sprintf(` 22 | resource "mssql_azuread_user" %[1]q { 23 | name = %[2]q 24 | database_id = %[4]d 25 | user_object_id = %[3]q 26 | } 27 | `, resourceName, name, testCtx.AzureADTestGroup.Id, testCtx.DefaultDBId) 28 | } 29 | 30 | testCtx.Test(resource.TestCase{ 31 | Steps: []resource.TestStep{ 32 | { 33 | Config: newResource("test_user", "test_aad_user"), 34 | Check: resource.ComposeTestCheckFunc( 35 | testCtx.SqlCheckDefaultDB(func(db *sql.DB) error { 36 | if err := db.QueryRow("SELECT principal_id FROM sys.database_principals WHERE [name] = 'test_aad_user'").Scan(&userId); err != nil { 37 | return err 38 | } 39 | 40 | userResourceId = fmt.Sprintf("%d/%d", testCtx.DefaultDBId, userId) 41 | 42 | return nil 43 | }), 44 | resource.ComposeAggregateTestCheckFunc( 45 | resource.TestCheckResourceAttrPtr("mssql_azuread_user.test_user", "id", &userResourceId), 46 | testCtx.SqlCheckDefaultDB(func(db *sql.DB) error { 47 | var userType, userSid string 48 | err := db.QueryRow("SELECT [type], CONVERT(VARCHAR(36), CONVERT(UNIQUEIDENTIFIER, [sid], 1), 1) FROM sys.database_principals WHERE principal_id = @p1", userId). 49 | Scan(&userType, &userSid) 50 | 51 | testCtx.Assert.Equal("E", strings.ToUpper(userType), "user type") 52 | testCtx.Assert.Equal(strings.ToUpper(testCtx.AzureADTestGroup.Id), strings.ToUpper(userSid), "user SID") 53 | 54 | return err 55 | }), 56 | ), 57 | ), 58 | }, 59 | { 60 | ResourceName: "mssql_azuread_user.test_user", 61 | ImportState: true, 62 | ImportStateIdFunc: func(*terraform.State) (string, error) { 63 | return userResourceId, nil 64 | }, 65 | ImportStateCheck: func(states []*terraform.InstanceState) error { 66 | for _, state := range states { 67 | if state.ID == userResourceId { 68 | testCtx.Assert.Equal("test_aad_user", state.Attributes["name"]) 69 | testCtx.Assert.Equal(fmt.Sprint(testCtx.DefaultDBId), state.Attributes["database_id"]) 70 | testCtx.Assert.Equal(strings.ToUpper(testCtx.AzureADTestGroup.Id), strings.ToUpper(state.Attributes["user_object_id"])) 71 | } 72 | } 73 | 74 | return nil 75 | }, 76 | }, 77 | }, 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /internal/services/common/attributes.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const RegularIdentifiersDoc = "Must follow [Regular Identifiers rules](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers#rules-for-regular-identifiers)" 4 | 5 | var AttributeDescriptions = map[string]string{ 6 | "database_id": "ID of database. Can be retrieved using `mssql_database` or `SELECT DB_ID('')`.", 7 | } 8 | -------------------------------------------------------------------------------- /internal/services/common/helpers.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 7 | "github.com/hashicorp/terraform-plugin-framework/attr" 8 | "strconv" 9 | ) 10 | 11 | func GetResourceDb(ctx context.Context, conn sql.Connection, dbId string) sql.Database { 12 | if dbId == "" { 13 | return sql.GetDatabaseByName(ctx, conn, "master") 14 | } 15 | 16 | id, err := strconv.Atoi(dbId) 17 | if err != nil { 18 | utils.AddError(ctx, "Failed to convert DB ID", err) 19 | return nil 20 | } 21 | 22 | return sql.GetDatabase(ctx, conn, sql.DatabaseId(id)) 23 | } 24 | 25 | func IsAttrSet[T attr.Value](attr T) bool { 26 | return !attr.IsUnknown() && !attr.IsNull() 27 | } 28 | -------------------------------------------------------------------------------- /internal/services/common/types.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | type DbObjectId[T sql.NumericObjectId] struct { 13 | DbId sql.DatabaseId 14 | ObjectId T 15 | IsEmpty bool 16 | } 17 | 18 | func (id DbObjectId[T]) String() string { 19 | return fmt.Sprintf("%v/%v", id.DbId, id.ObjectId) 20 | } 21 | 22 | func ParseDbObjectId[T sql.NumericObjectId](ctx context.Context, s string) DbObjectId[T] { 23 | var res DbObjectId[T] 24 | segments := parseIdSegments(ctx, s) 25 | 26 | if len(segments) < 2 || utils.HasError(ctx) { 27 | res.IsEmpty = true 28 | } else { 29 | res.DbId = sql.DatabaseId(segments[0]) 30 | res.ObjectId = T(segments[1]) 31 | } 32 | 33 | return res 34 | } 35 | 36 | type DbObjectMemberId[TObject sql.NumericObjectId, TMember sql.NumericObjectId] struct { 37 | DbObjectId[TObject] 38 | MemberId TMember 39 | } 40 | 41 | func ParseDbObjectMemberId[TObject sql.NumericObjectId, TMember sql.NumericObjectId](ctx context.Context, s string) DbObjectMemberId[TObject, TMember] { 42 | res := DbObjectMemberId[TObject, TMember]{DbObjectId: ParseDbObjectId[TObject](ctx, s)} 43 | segments := parseIdSegments(ctx, s) 44 | 45 | if len(segments) < 3 || utils.HasError(ctx) { 46 | res.IsEmpty = true 47 | } else { 48 | res.MemberId = TMember(segments[2]) 49 | } 50 | 51 | return res 52 | } 53 | 54 | func (id DbObjectMemberId[TObject, TMember]) String() string { 55 | return fmt.Sprintf("%s/%d", id.DbObjectId, id.MemberId) 56 | } 57 | 58 | func (id DbObjectMemberId[TObject, TMember]) GetMemberId() DbObjectId[TMember] { 59 | return DbObjectId[TMember]{DbId: id.DbId, ObjectId: id.MemberId} 60 | } 61 | 62 | func parseIdSegments(ctx context.Context, s string) []int { 63 | var segments []int 64 | 65 | for _, seg := range strings.Split(s, "/") { 66 | if seg != "" { 67 | num, err := strconv.Atoi(seg) 68 | if err != nil { 69 | utils.AddError(ctx, fmt.Sprintf("Failed to parse DB object ID %q", s), err) 70 | return nil 71 | } 72 | 73 | segments = append(segments, num) 74 | } 75 | } 76 | 77 | return segments 78 | } 79 | -------------------------------------------------------------------------------- /internal/services/database/base.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 8 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | "strconv" 11 | ) 12 | 13 | var attrDescriptions = map[string]string{ 14 | "id": "Database ID. Can be retrieved using `SELECT DB_ID('')`.", 15 | "name": fmt.Sprintf("Database name. %s.", common.RegularIdentifiersDoc), 16 | "collation": "Default collation name. Can be either a Windows collation name or a SQL collation name.", 17 | } 18 | 19 | type resourceData struct { 20 | Id types.String `tfsdk:"id"` 21 | Name types.String `tfsdk:"name"` 22 | Collation types.String `tfsdk:"collation"` 23 | } 24 | 25 | func (d resourceData) getDbId(ctx context.Context) sql.DatabaseId { 26 | if !common.IsAttrSet(d.Id) { 27 | return sql.NullDatabaseId 28 | } 29 | 30 | id, err := strconv.Atoi(d.Id.ValueString()) 31 | 32 | if err != nil { 33 | utils.AddError(ctx, fmt.Sprintf("Failed to convert resource ID '%s'", d.Id.ValueString()), err) 34 | } 35 | 36 | return sql.DatabaseId(id) 37 | } 38 | 39 | func (d resourceData) toSettings() sql.DatabaseSettings { 40 | return sql.DatabaseSettings{ 41 | Name: d.Name.ValueString(), 42 | Collation: d.Collation.ValueString(), 43 | } 44 | } 45 | 46 | func (d resourceData) withSettings(settings sql.DatabaseSettings) resourceData { 47 | resData := resourceData{ 48 | Id: d.Id, 49 | Name: types.StringValue(settings.Name), 50 | Collation: types.StringValue(settings.Collation), 51 | } 52 | 53 | if settings.Collation == "" { 54 | resData.Collation = types.StringNull() 55 | } 56 | 57 | return resData 58 | } 59 | -------------------------------------------------------------------------------- /internal/services/database/data.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 8 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 9 | "github.com/hashicorp/terraform-plugin-framework/datasource/schema" 10 | 11 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 12 | "github.com/hashicorp/terraform-plugin-framework/types" 13 | ) 14 | 15 | type dataSource struct{} 16 | 17 | func (d *dataSource) GetName() string { 18 | return "database" 19 | } 20 | 21 | func (d *dataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { 22 | resp.Schema.MarkdownDescription = "Obtains information about single database." 23 | resp.Schema.Attributes = map[string]schema.Attribute{ 24 | "id": schema.StringAttribute{ 25 | MarkdownDescription: attrDescriptions["id"], 26 | Computed: true, 27 | }, 28 | "name": schema.StringAttribute{ 29 | MarkdownDescription: attrDescriptions["name"], 30 | Required: true, 31 | }, 32 | "collation": schema.StringAttribute{ 33 | MarkdownDescription: attrDescriptions["collation"], 34 | Computed: true, 35 | }, 36 | } 37 | } 38 | 39 | func (d *dataSource) Read(ctx context.Context, req datasource.ReadRequest[resourceData], resp *datasource.ReadResponse[resourceData]) { 40 | var db sql.Database 41 | 42 | req. 43 | Then(func() { 44 | db = sql.GetDatabaseByName(ctx, req.Conn, req.Config.Name.ValueString()) 45 | 46 | if db == nil || !db.Exists(ctx) { 47 | utils.AddError(ctx, "DB does not exist", fmt.Errorf("could not find DB '%s'", req.Config.Name.ValueString())) 48 | } 49 | }). 50 | Then(func() { 51 | state := req.Config.withSettings(db.GetSettings(ctx)) 52 | 53 | if !common.IsAttrSet(state.Id) { 54 | state.Id = types.StringValue(fmt.Sprint(db.GetId(ctx))) 55 | } 56 | 57 | resp.SetState(state) 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /internal/services/database/data_acctest.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | "regexp" 9 | ) 10 | 11 | func testDataSource(testCtx *acctest.TestContext) { 12 | const resourceName = "data.mssql_database.test" 13 | var dbId string 14 | dbSettings := sql.DatabaseSettings{Name: "data_test_db", Collation: "SQL_Latin1_General_CP1_CS_AS"} 15 | 16 | newDataResource := func(name string) string { 17 | return fmt.Sprintf(` 18 | data "mssql_database" "test" { 19 | name = %q 20 | }`, name) 21 | } 22 | 23 | testCtx.Test(resource.TestCase{ 24 | PreCheck: func() { 25 | dbId = fmt.Sprint(testCtx.CreateDB(dbSettings.Name)) 26 | _, err := testCtx.GetDBConnection(dbSettings.Name).Exec("ALTER DATABASE [data_test_db] COLLATE SQL_Latin1_General_CP1_CS_AS") 27 | testCtx.Require.NoError(err, "Setting DB collation") 28 | }, 29 | Steps: []resource.TestStep{ 30 | { 31 | Config: newDataResource("not_exists"), 32 | ExpectError: regexp.MustCompile("not exist"), 33 | }, 34 | { 35 | Config: newDataResource("data_test_db"), 36 | Check: resource.ComposeAggregateTestCheckFunc( 37 | resource.TestCheckResourceAttrPtr(resourceName, "id", &dbId), 38 | resource.TestCheckResourceAttr(resourceName, "name", dbSettings.Name), 39 | resource.TestCheckResourceAttr(resourceName, "collation", dbSettings.Collation), 40 | ), 41 | }, 42 | }, 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /internal/services/database/list.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 8 | "github.com/hashicorp/terraform-plugin-framework/datasource/schema" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | ) 11 | 12 | type listDataSourceData struct { 13 | Id types.String `tfsdk:"id"` 14 | Databases []resourceData `tfsdk:"databases"` 15 | } 16 | 17 | type listDataSource struct{} 18 | 19 | func (l *listDataSource) GetName() string { 20 | return "databases" 21 | } 22 | 23 | func (l *listDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { 24 | resp.Schema.MarkdownDescription = "Obtains information about all databases found in SQL Server instance." 25 | resp.Schema.Attributes = map[string]schema.Attribute{ 26 | "id": schema.StringAttribute{ 27 | Description: "ID of the resource used only internally by the provider.", 28 | Computed: true, 29 | }, 30 | "databases": schema.SetNestedAttribute{ 31 | Description: "Set of database objects", 32 | Computed: true, 33 | NestedObject: schema.NestedAttributeObject{ 34 | Attributes: map[string]schema.Attribute{ 35 | "id": schema.StringAttribute{ 36 | MarkdownDescription: attrDescriptions["id"], 37 | Computed: true, 38 | }, 39 | "name": schema.StringAttribute{ 40 | MarkdownDescription: attrDescriptions["name"], 41 | Computed: true, 42 | }, 43 | "collation": schema.StringAttribute{ 44 | MarkdownDescription: attrDescriptions["collation"], 45 | Computed: true, 46 | }, 47 | }, 48 | }, 49 | }, 50 | } 51 | } 52 | 53 | func (l *listDataSource) Read(ctx context.Context, req datasource.ReadRequest[listDataSourceData], resp *datasource.ReadResponse[listDataSourceData]) { 54 | var dbs map[sql.DatabaseId]sql.Database 55 | 56 | req. 57 | Then(func() { dbs = sql.GetDatabases(ctx, req.Conn) }). 58 | Then(func() { 59 | result := listDataSourceData{ 60 | Id: types.StringValue(""), 61 | Databases: []resourceData{}, 62 | } 63 | 64 | for id, db := range dbs { 65 | r := resourceData{ 66 | Id: types.StringValue(fmt.Sprint(id)), 67 | } 68 | result.Databases = append(result.Databases, r.withSettings(db.GetSettings(ctx))) 69 | } 70 | 71 | resp.SetState(result) 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /internal/services/database/list_acctest.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | ) 8 | 9 | func testListDataSource(testCtx *acctest.TestContext) { 10 | const resourceName = "data.mssql_databases.list" 11 | 12 | var checkPredefinedDB = func(id int, name string) resource.TestCheckFunc { 13 | return resource.TestCheckTypeSetElemNestedAttrs(resourceName, "databases.*", map[string]string{ 14 | "id": fmt.Sprint(id), 15 | "name": name, 16 | "collation": "SQL_Latin1_General_CP1_CI_AS", 17 | }) 18 | } 19 | 20 | testCtx.Test(resource.TestCase{ 21 | Steps: []resource.TestStep{ 22 | { 23 | Config: `data "mssql_databases" "list" {}`, 24 | Check: resource.ComposeAggregateTestCheckFunc( 25 | checkPredefinedDB(1, "master"), 26 | ), 27 | }, 28 | }, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /internal/services/database/main.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkresource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "database" 19 | } 20 | 21 | func (s service) Resources() []func() sdkresource.ResourceWithConfigure { 22 | return []func() sdkresource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[resourceData](&dataSource{}), 30 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 31 | } 32 | } 33 | 34 | func (s service) Tests() core.AccTests { 35 | return core.AccTests{ 36 | DataSource: testDataSource, 37 | ListDataSource: testListDataSource, 38 | Resource: testResource, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/services/databasePermission/base.go: -------------------------------------------------------------------------------- 1 | package databasePermission 2 | 3 | var attrDescriptions = map[string]string{ 4 | "id": "`//`.", 5 | "principal_id": "`/`. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal`.", 6 | "permission": "Name of database-level SQL permission. For full list of supported permissions, see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql?view=azuresqldb-current#remarks)", 7 | "with_grant_option": "When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals.", 8 | } 9 | -------------------------------------------------------------------------------- /internal/services/databasePermission/list_acctest.go: -------------------------------------------------------------------------------- 1 | package databasePermission 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | ) 8 | 9 | func testListDataSource(testCtx *acctest.TestContext) { 10 | testCtx.ExecDefaultDB("CREATE ROLE [test_db_permissions_list]") 11 | defer testCtx.ExecDefaultDB("DROP ROLE [test_db_permissions_list]") 12 | 13 | var roleId int 14 | err := testCtx.GetDefaultDBConnection().QueryRow("SELECT principal_id FROM sys.database_principals WHERE [name]='test_db_permissions_list'").Scan(&roleId) 15 | testCtx.Require.NoError(err, "Fetching IDs") 16 | 17 | testCtx.ExecDefaultDB("GRANT DELETE TO [test_db_permissions_list]; GRANT ALTER TO [test_db_permissions_list] WITH GRANT OPTION") 18 | 19 | testCtx.Test(resource.TestCase{ 20 | Steps: []resource.TestStep{ 21 | { 22 | Config: fmt.Sprintf(` 23 | data "mssql_database_permissions" "test" { 24 | principal_id = %q 25 | } 26 | `, testCtx.DefaultDbId(roleId)), 27 | 28 | Check: resource.ComposeAggregateTestCheckFunc( 29 | resource.TestCheckTypeSetElemNestedAttrs("data.mssql_database_permissions.test", "permissions.*", map[string]string{ 30 | "permission": "DELETE", 31 | "with_grant_option": "false", 32 | }), 33 | resource.TestCheckTypeSetElemNestedAttrs("data.mssql_database_permissions.test", "permissions.*", map[string]string{ 34 | "permission": "ALTER", 35 | "with_grant_option": "true", 36 | }), 37 | ), 38 | }, 39 | }, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /internal/services/databasePermission/main.go: -------------------------------------------------------------------------------- 1 | package databasePermission 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkresource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "database_permission" 19 | } 20 | 21 | func (s service) Resources() []func() sdkresource.ResourceWithConfigure { 22 | return []func() sdkresource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 30 | } 31 | } 32 | 33 | func (s service) Tests() core.AccTests { 34 | return core.AccTests{ 35 | ListDataSource: testListDataSource, 36 | Resource: testResource, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/services/databaseRole/data.go: -------------------------------------------------------------------------------- 1 | package databaseRole 2 | 3 | import ( 4 | "context" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | common2 "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 8 | "github.com/hashicorp/terraform-plugin-framework/datasource/schema" 9 | ) 10 | 11 | type dataSource struct{} 12 | 13 | func (d *dataSource) GetName() string { 14 | return "database_role" 15 | } 16 | 17 | func (d *dataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { 18 | resp.Schema.MarkdownDescription = "Obtains information about single database role." 19 | resp.Schema.Attributes = map[string]schema.Attribute{ 20 | "id": schema.StringAttribute{ 21 | MarkdownDescription: roleAttributeDescriptions["id"], 22 | Computed: true, 23 | }, 24 | "name": schema.StringAttribute{ 25 | MarkdownDescription: roleAttributeDescriptions["name"], 26 | Required: true, 27 | }, 28 | "database_id": schema.StringAttribute{ 29 | MarkdownDescription: common2.AttributeDescriptions["database_id"] + " Defaults to ID of `master`.", 30 | Optional: true, 31 | }, 32 | "owner_id": schema.StringAttribute{ 33 | MarkdownDescription: roleAttributeDescriptions["owner_id"], 34 | Computed: true, 35 | }, 36 | "members": schema.SetNestedAttribute{ 37 | MarkdownDescription: "Set of role members", 38 | Computed: true, 39 | NestedObject: schema.NestedAttributeObject{ 40 | Attributes: map[string]schema.Attribute{ 41 | "id": schema.StringAttribute{ 42 | MarkdownDescription: "`/`. Member ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')", 43 | Computed: true, 44 | }, 45 | "name": schema.StringAttribute{ 46 | Description: "Name of the database principal.", 47 | Computed: true, 48 | }, 49 | "type": schema.StringAttribute{ 50 | Description: "One of: `SQL_USER`, `DATABASE_ROLE`, `AZUREAD_USER`", 51 | Computed: true, 52 | }, 53 | }, 54 | }, 55 | }, 56 | } 57 | } 58 | 59 | func (d *dataSource) Read(ctx context.Context, req datasource.ReadRequest[dataSourceData], resp *datasource.ReadResponse[dataSourceData]) { 60 | var ( 61 | db sql.Database 62 | role sql.DatabaseRole 63 | ) 64 | 65 | req. 66 | Then(func() { db = common2.GetResourceDb(ctx, req.Conn, req.Config.DatabaseId.ValueString()) }). 67 | Then(func() { role = sql.GetDatabaseRoleByName(ctx, db, req.Config.Name.ValueString()) }). 68 | Then(func() { resp.SetState(req.Config.withRoleData(ctx, role)) }) 69 | } 70 | -------------------------------------------------------------------------------- /internal/services/databaseRole/list_acctest.go: -------------------------------------------------------------------------------- 1 | package databaseRole 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | ) 9 | 10 | func testListDataSource(testCtx *acctest.TestContext) { 11 | var roleResourceId, ownerResourceId string 12 | 13 | defer testCtx.ExecDefaultDB(` 14 | DROP ROLE [test_role]; 15 | DROP ROLE [test_owner]; 16 | `) 17 | 18 | testCtx.Test(resource.TestCase{ 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: `data "mssql_database_roles" "master" {}`, 22 | Check: resource.TestCheckTypeSetElemNestedAttrs("data.mssql_database_roles.master", "roles.*", map[string]string{ 23 | "id": "1/0", 24 | "name": "public", 25 | "database_id": "1", 26 | "owner_id": "1/1", 27 | }), 28 | }, 29 | { 30 | PreConfig: func() { 31 | conn := testCtx.GetDefaultDBConnection() 32 | var roleId, ownerId int 33 | err := conn.QueryRow(` 34 | CREATE ROLE test_owner; 35 | CREATE ROLE test_role AUTHORIZATION test_owner; 36 | SELECT DATABASE_PRINCIPAL_ID('test_role'), DATABASE_PRINCIPAL_ID('test_owner'); 37 | `).Scan(&roleId, &ownerId) 38 | 39 | testCtx.Require.NoError(err, "creating role") 40 | 41 | roleResourceId = fmt.Sprintf("%d/%d", testCtx.DefaultDBId, roleId) 42 | ownerResourceId = fmt.Sprintf("%d/%d", testCtx.DefaultDBId, ownerId) 43 | }, 44 | Config: fmt.Sprintf(` 45 | data "mssql_database_roles" "test" { 46 | database_id = %d 47 | } 48 | `, testCtx.DefaultDBId), 49 | Check: func(state *terraform.State) error { 50 | return resource.TestCheckTypeSetElemNestedAttrs("data.mssql_database_roles.test", "roles.*", map[string]string{ 51 | "id": roleResourceId, 52 | "name": "test_role", 53 | "database_id": fmt.Sprint(testCtx.DefaultDBId), 54 | "owner_id": ownerResourceId, 55 | })(state) 56 | }, 57 | }, 58 | }, 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /internal/services/databaseRole/main.go: -------------------------------------------------------------------------------- 1 | package databaseRole 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkresource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "database_role" 19 | } 20 | 21 | func (s service) Resources() []func() sdkresource.ResourceWithConfigure { 22 | return []func() sdkresource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[dataSourceData](&dataSource{}), 30 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 31 | } 32 | } 33 | 34 | func (s service) Tests() core.AccTests { 35 | return core.AccTests{ 36 | DataSource: testDataSource, 37 | ListDataSource: testListDataSource, 38 | Resource: testResource, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/services/databaseRoleMember/main.go: -------------------------------------------------------------------------------- 1 | package databaseRoleMember 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 6 | "github.com/hashicorp/terraform-plugin-framework/datasource" 7 | sdkResource "github.com/hashicorp/terraform-plugin-framework/resource" 8 | ) 9 | 10 | func Service() core.Service { 11 | return service{} 12 | } 13 | 14 | type service struct{} 15 | 16 | func (s service) Name() string { 17 | return "database_role_member" 18 | } 19 | 20 | func (s service) Resources() []func() sdkResource.ResourceWithConfigure { 21 | return []func() sdkResource.ResourceWithConfigure{ 22 | resource.NewResource[resourceData](&res{}), 23 | } 24 | } 25 | 26 | func (s service) DataSources() []func() datasource.DataSourceWithConfigure { 27 | return []func() datasource.DataSourceWithConfigure{} 28 | } 29 | 30 | func (s service) Tests() core.AccTests { 31 | return core.AccTests{ 32 | Resource: testResource, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/services/schema/base.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 8 | "github.com/hashicorp/terraform-plugin-framework/types" 9 | ) 10 | 11 | var attrDescriptions = map[string]string{ 12 | "id": "`/`. Schema ID can be retrieved using `SELECT SCHEMA_ID('')`.", 13 | "name": "Schema name.", 14 | "owner_id": "ID of database role or user owning this schema. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal`", 15 | } 16 | 17 | type resourceData struct { 18 | Id types.String `tfsdk:"id"` 19 | DatabaseId types.String `tfsdk:"database_id"` 20 | Name types.String `tfsdk:"name"` 21 | OwnerId types.String `tfsdk:"owner_id"` 22 | } 23 | 24 | func (d resourceData) withSchemaData(ctx context.Context, schema sql.Schema) resourceData { 25 | dbId := schema.GetDb(ctx).GetId(ctx) 26 | 27 | return resourceData{ 28 | Id: types.StringValue(common.DbObjectId[sql.SchemaId]{DbId: dbId, ObjectId: schema.GetId(ctx)}.String()), 29 | Name: types.StringValue(schema.GetName(ctx)), 30 | DatabaseId: types.StringValue(fmt.Sprint(dbId)), 31 | OwnerId: types.StringValue(common.DbObjectId[sql.GenericDatabasePrincipalId]{DbId: dbId, ObjectId: schema.GetOwnerId(ctx)}.String()), 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/services/schema/data_acctest.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "regexp" 8 | ) 9 | 10 | func testDataSource(testCtx *acctest.TestContext) { 11 | testCtx.ExecDefaultDB("CREATE ROLE test_schema_ds_owner") 12 | testCtx.ExecDefaultDB("CREATE SCHEMA test_schema_ds AUTHORIZATION test_schema_ds_owner") 13 | defer func() { 14 | testCtx.ExecDefaultDB("DROP SCHEMA test_schema_ds") 15 | testCtx.ExecDefaultDB("DROP ROLE test_schema_ds_owner") 16 | }() 17 | 18 | var schemaId, ownerId string 19 | err := testCtx.GetDefaultDBConnection().QueryRow("SELECT SCHEMA_ID('test_schema_ds'), USER_ID('test_schema_ds_owner')").Scan(&schemaId, &ownerId) 20 | testCtx.Require.NoError(err, "Fetching IDs") 21 | 22 | testCtx.Test(resource.TestCase{ 23 | Steps: []resource.TestStep{ 24 | { 25 | Config: fmt.Sprintf(` 26 | data "mssql_schema" "by_name" { 27 | database_id = %d 28 | name = "test_schema_ds" 29 | } 30 | `, testCtx.DefaultDBId), 31 | 32 | Check: resource.ComposeAggregateTestCheckFunc( 33 | resource.TestCheckResourceAttr("data.mssql_schema.by_name", "id", testCtx.DefaultDbId(schemaId)), 34 | resource.TestCheckResourceAttr("data.mssql_schema.by_name", "owner_id", testCtx.DefaultDbId(ownerId)), 35 | ), 36 | }, 37 | { 38 | Config: fmt.Sprintf(` 39 | data "mssql_schema" "by_id" { 40 | id = %q 41 | } 42 | `, testCtx.DefaultDbId(schemaId)), 43 | 44 | Check: resource.ComposeAggregateTestCheckFunc( 45 | resource.TestCheckResourceAttr("data.mssql_schema.by_id", "name", "test_schema_ds"), 46 | resource.TestCheckResourceAttr("data.mssql_schema.by_id", "database_id", fmt.Sprint(testCtx.DefaultDBId)), 47 | resource.TestCheckResourceAttr("data.mssql_schema.by_id", "owner_id", testCtx.DefaultDbId(ownerId)), 48 | ), 49 | }, 50 | { 51 | Config: fmt.Sprintf(` 52 | data "mssql_schema" "not_exist" { 53 | database_id = %d 54 | name = "not_existsing_schema" 55 | } 56 | `, testCtx.DefaultDBId), 57 | 58 | ExpectError: regexp.MustCompile("not exist"), 59 | }, 60 | }, 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /internal/services/schema/list_acctest.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | ) 8 | 9 | func testListDataSource(testCtx *acctest.TestContext) { 10 | testCtx.ExecDefaultDB("CREATE SCHEMA [test_list]") 11 | defer testCtx.ExecDefaultDB("DROP SCHEMA [test_list]") 12 | 13 | var schemaId, ownerId int 14 | err := testCtx.GetDefaultDBConnection().QueryRow("SELECT schema_id, principal_id FROM sys.schemas WHERE [name]='test_list'").Scan(&schemaId, &ownerId) 15 | testCtx.Require.NoError(err, "Fetching IDs") 16 | 17 | testCtx.Test(resource.TestCase{ 18 | Steps: []resource.TestStep{ 19 | { 20 | Config: fmt.Sprintf(` 21 | data "mssql_schemas" "all" { 22 | database_id = %d 23 | } 24 | `, testCtx.DefaultDBId), 25 | 26 | Check: resource.TestCheckTypeSetElemNestedAttrs("data.mssql_schemas.all", "schemas.*", map[string]string{ 27 | "id": testCtx.DefaultDbId(schemaId), 28 | "owner_id": testCtx.DefaultDbId(ownerId), 29 | "database_id": fmt.Sprint(testCtx.DefaultDBId), 30 | "name": "test_list", 31 | }), 32 | }, 33 | }, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /internal/services/schema/main.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkresource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "schema" 19 | } 20 | 21 | func (s service) Resources() []func() sdkresource.ResourceWithConfigure { 22 | return []func() sdkresource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[resourceData](&dataSource{}), 30 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 31 | } 32 | } 33 | 34 | func (s service) Tests() core.AccTests { 35 | return core.AccTests{ 36 | DataSource: testDataSource, 37 | ListDataSource: testListDataSource, 38 | Resource: testResource, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/services/schemaPermission/base.go: -------------------------------------------------------------------------------- 1 | package schemaPermission 2 | 3 | var attrDescriptions = map[string]string{ 4 | "id": "`///`.", 5 | "schema_id": "`/`. Can be retrieved using `mssql_schema`.", 6 | "principal_id": "`/`. Can be retrieved using `mssql_database_role`, `mssql_sql_user`, `mssql_azuread_user` or `mssql_azuread_service_principal`.", 7 | "permission": "Name of schema SQL permission. For full list of supported permissions, see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-schema-permissions-transact-sql?view=azuresqldb-current#remarks)", 8 | "with_grant_option": "When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals.", 9 | } 10 | -------------------------------------------------------------------------------- /internal/services/schemaPermission/list_acctest.go: -------------------------------------------------------------------------------- 1 | package schemaPermission 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | ) 8 | 9 | func testListDataSource(testCtx *acctest.TestContext) { 10 | testCtx.ExecDefaultDB("CREATE SCHEMA [test_perm_list]") 11 | defer testCtx.ExecDefaultDB("DROP SCHEMA [test_perm_list]") 12 | 13 | testCtx.ExecDefaultDB("CREATE ROLE [test_perm_list_role]") 14 | defer testCtx.ExecDefaultDB("DROP ROLE [test_perm_list_role]") 15 | 16 | testCtx.ExecDefaultDB("GRANT DELETE ON schema::[test_perm_list] TO [test_perm_list_role]") 17 | testCtx.ExecDefaultDB("GRANT ALTER ON schema::[test_perm_list] TO [test_perm_list_role] WITH GRANT OPTION") 18 | 19 | var schemaId, roleId int 20 | err := testCtx.GetDefaultDBConnection(). 21 | QueryRow("SELECT SCHEMA_ID('test_perm_list'), DATABASE_PRINCIPAL_ID('test_perm_list_role')"). 22 | Scan(&schemaId, &roleId) 23 | testCtx.Require.NoError(err, "Fetching IDs") 24 | 25 | testCtx.Test(resource.TestCase{ 26 | Steps: []resource.TestStep{ 27 | { 28 | Config: fmt.Sprintf(` 29 | data "mssql_schema_permissions" "test" { 30 | schema_id = %q 31 | principal_id = %q 32 | } 33 | `, testCtx.DefaultDbId(schemaId), testCtx.DefaultDbId(roleId)), 34 | 35 | Check: resource.ComposeAggregateTestCheckFunc( 36 | resource.TestCheckTypeSetElemNestedAttrs("data.mssql_schema_permissions.test", "permissions.*", map[string]string{ 37 | "permission": "DELETE", 38 | "with_grant_option": "false", 39 | }), 40 | resource.TestCheckTypeSetElemNestedAttrs("data.mssql_schema_permissions.test", "permissions.*", map[string]string{ 41 | "permission": "ALTER", 42 | "with_grant_option": "true", 43 | }), 44 | ), 45 | }, 46 | }, 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /internal/services/schemaPermission/main.go: -------------------------------------------------------------------------------- 1 | package schemaPermission 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkresource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "schema_permission" 19 | } 20 | 21 | func (s service) Resources() []func() sdkresource.ResourceWithConfigure { 22 | return []func() sdkresource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 30 | } 31 | } 32 | 33 | func (s service) Tests() core.AccTests { 34 | return core.AccTests{ 35 | ListDataSource: testListDataSource, 36 | Resource: testResource, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/services/script/data.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "context" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 8 | "github.com/hashicorp/terraform-plugin-framework/datasource/schema" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | ) 11 | 12 | type dataSourceData struct { 13 | Id types.String `tfsdk:"id"` 14 | DatabaseId types.String `tfsdk:"database_id"` 15 | Query types.String `tfsdk:"query"` 16 | Result []map[string]string `tfsdk:"result"` 17 | } 18 | 19 | type dataSource struct{} 20 | 21 | func (d *dataSource) GetName() string { 22 | return "query" 23 | } 24 | 25 | func (d *dataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { 26 | resp.Schema.MarkdownDescription = ` 27 | Retrieves arbitrary SQL query result. 28 | 29 | -> **Note** This data source is meant to be an escape hatch for all cases not supported by the provider's data sources. Whenever possible, use dedicated data sources, which offer better plan, validation and error reporting. 30 | ` 31 | 32 | resp.Schema.Attributes = map[string]schema.Attribute{ 33 | "id": schema.StringAttribute{ 34 | MarkdownDescription: "Used only internally by Terraform. Always set to `query`", 35 | Computed: true, 36 | }, 37 | "database_id": schema.StringAttribute{ 38 | MarkdownDescription: common.AttributeDescriptions["database_id"], 39 | Required: true, 40 | }, 41 | "query": schema.StringAttribute{ 42 | MarkdownDescription: "SQL query returning single result set, with any number of rows, where all columns are strings", 43 | Required: true, 44 | }, 45 | "result": schema.ListAttribute{ 46 | MarkdownDescription: "Results of the SQL query, represented as list of maps, where the map key corresponds to column name and the value is the value of column in given row.", 47 | Computed: true, 48 | ElementType: types.MapType{ElemType: types.StringType}, 49 | }, 50 | } 51 | } 52 | 53 | func (d *dataSource) Read(ctx context.Context, req datasource.ReadRequest[dataSourceData], resp *datasource.ReadResponse[dataSourceData]) { 54 | var ( 55 | db sql.Database 56 | result []map[string]string 57 | ) 58 | 59 | req. 60 | Then(func() { db = common.GetResourceDb(ctx, req.Conn, req.Config.DatabaseId.ValueString()) }). 61 | Then(func() { result = db.Query(ctx, req.Config.Query.ValueString()) }). 62 | Then(func() { 63 | req.Config.Result = result 64 | req.Config.Id = types.StringValue("query") 65 | resp.SetState(req.Config) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /internal/services/script/data_acctest.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | ) 9 | 10 | func testDataSource(testCtx *acctest.TestContext) { 11 | 12 | newConfig := func(resName string, query string) string { 13 | return fmt.Sprintf(` 14 | data "mssql_query" %[1]q { 15 | database_id = %[3]d 16 | query = %[2]q 17 | } 18 | `, resName, query, testCtx.DefaultDBId) 19 | } 20 | 21 | testCtx.Test(resource.TestCase{ 22 | Steps: []resource.TestStep{ 23 | { 24 | Config: newConfig("multirow", "SELECT 1 AS X, 'FOO' AS Y UNION ALL SELECT 3, 'BAR'"), 25 | Check: func(state *terraform.State) error { 26 | testAttr := func(name string, value string) resource.TestCheckFunc { 27 | return resource.TestCheckResourceAttr("data.mssql_query.multirow", name, value) 28 | } 29 | 30 | return resource.ComposeAggregateTestCheckFunc( 31 | testAttr("result.#", "2"), 32 | testAttr("result.0.X", "1"), 33 | testAttr("result.0.Y", "FOO"), 34 | testAttr("result.1.X", "3"), 35 | testAttr("result.1.Y", "BAR"), 36 | )(state) 37 | }, 38 | }, 39 | { 40 | Config: newConfig("null", "SELECT 1 AS X, NULL AS Y"), 41 | Check: resource.TestCheckNoResourceAttr("data.mssql_query.null", "result.0.Y"), 42 | }, 43 | { 44 | Config: newConfig("no_rows", "SELECT 1 WHERE 1=0"), 45 | Check: resource.TestCheckResourceAttr("data.mssql_query.no_rows", "result.#", "0"), 46 | }, 47 | }, 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /internal/services/script/main.go: -------------------------------------------------------------------------------- 1 | package script 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkResource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "script" 19 | } 20 | 21 | func (s service) Resources() []func() sdkResource.ResourceWithConfigure { 22 | return []func() sdkResource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[dataSourceData](&dataSource{}), 30 | } 31 | } 32 | 33 | func (s service) Tests() core.AccTests { 34 | return core.AccTests{ 35 | Resource: testResource, 36 | DataSource: testDataSource, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/services/serverPermission/base.go: -------------------------------------------------------------------------------- 1 | package serverPermission 2 | 3 | var attrDescriptions = map[string]string{ 4 | "id": "`/`", 5 | "principal_id": "ID of the principal who will be granted `permission`. Can be retrieved using `mssql_server_role` or `mssql_sql_login`.", 6 | "permission": "Name of server-level SQL permission. For full list of supported permissions see [docs](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-server-permissions-transact-sql?view=azuresqldb-current#remarks)", 7 | "with_grant_option": "When set to `true`, `principal_id` will be allowed to grant the `permission` to other principals.", 8 | } 9 | -------------------------------------------------------------------------------- /internal/services/serverPermission/list_acctest.go: -------------------------------------------------------------------------------- 1 | package serverPermission 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | ) 8 | 9 | func testListDataSource(testCtx *acctest.TestContext) { 10 | if testCtx.IsAzureTest { 11 | return 12 | } 13 | 14 | testCtx.ExecMasterDB("CREATE SERVER ROLE [server_perm_test]") 15 | defer testCtx.ExecMasterDB("DROP SERVER ROLE [server_perm_test]") 16 | testCtx.ExecMasterDB("GRANT VIEW ANY DATABASE TO [server_perm_test]") 17 | testCtx.ExecMasterDB("GRANT VIEW SERVER STATE TO [server_perm_test] WITH GRANT OPTION") 18 | 19 | var principalId string 20 | err := testCtx.GetMasterDBConnection().QueryRow("SELECT [principal_id] FROM sys.server_principals WHERE [name]='server_perm_test'").Scan(&principalId) 21 | testCtx.Require.NoError(err, "Fetching principal ID") 22 | 23 | testCtx.Test(resource.TestCase{ 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: fmt.Sprintf(` 27 | data "mssql_server_permissions" "test" { 28 | principal_id = %q 29 | } 30 | `, principalId), 31 | Check: resource.ComposeAggregateTestCheckFunc( 32 | resource.TestCheckTypeSetElemNestedAttrs("data.mssql_server_permissions.test", "permissions.*", map[string]string{ 33 | "permission": "VIEW ANY DATABASE", 34 | "with_grant_option": "false", 35 | }), 36 | resource.TestCheckTypeSetElemNestedAttrs("data.mssql_server_permissions.test", "permissions.*", map[string]string{ 37 | "permission": "VIEW SERVER STATE", 38 | "with_grant_option": "true", 39 | }), 40 | ), 41 | }, 42 | }, 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /internal/services/serverPermission/main.go: -------------------------------------------------------------------------------- 1 | package serverPermission 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkresource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "server_permission" 19 | } 20 | 21 | func (s service) Resources() []func() sdkresource.ResourceWithConfigure { 22 | return []func() sdkresource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 30 | } 31 | } 32 | 33 | func (s service) Tests() core.AccTests { 34 | return core.AccTests{ 35 | ListDataSource: testListDataSource, 36 | Resource: testResource, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/services/serverRole/base.go: -------------------------------------------------------------------------------- 1 | package serverRole 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | common2 "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 8 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | "strconv" 11 | ) 12 | 13 | var attrDescriptions = map[string]string{ 14 | "id": "Role principal ID.", 15 | "name": fmt.Sprintf("Role name. %s and cannot be longer than 128 chars.", common2.RegularIdentifiersDoc), 16 | "owner_id": "ID of another server role or login owning this role. Can be retrieved using `mssql_server_role` or `mssql_sql_login`.", 17 | } 18 | 19 | type resourceData struct { 20 | Id types.String `tfsdk:"id"` 21 | Name types.String `tfsdk:"name"` 22 | OwnerId types.String `tfsdk:"owner_id"` 23 | } 24 | 25 | func (d resourceData) withSettings(settings sql.ServerRoleSettings) resourceData { 26 | d.Name = types.StringValue(settings.Name) 27 | d.OwnerId = types.StringValue(fmt.Sprint(settings.OwnerId)) 28 | 29 | return d 30 | } 31 | 32 | func (d resourceData) toSettings(ctx context.Context) sql.ServerRoleSettings { 33 | settings := sql.ServerRoleSettings{ 34 | Name: d.Name.ValueString(), 35 | OwnerId: sql.EmptyServerPrincipalId, 36 | } 37 | 38 | if common2.IsAttrSet(d.OwnerId) { 39 | id, err := strconv.Atoi(d.OwnerId.ValueString()) 40 | utils.AddError(ctx, "Failed to parse owner ID", err) 41 | settings.OwnerId = sql.GenericServerPrincipalId(id) 42 | } 43 | 44 | return settings 45 | } 46 | 47 | func parseId(ctx context.Context, id types.String) sql.ServerRoleId { 48 | intId, err := strconv.Atoi(id.ValueString()) 49 | utils.AddError(ctx, "Failed to parse ID", err) 50 | return sql.ServerRoleId(intId) 51 | } 52 | -------------------------------------------------------------------------------- /internal/services/serverRole/common_acctest.go: -------------------------------------------------------------------------------- 1 | package serverRole 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | ) 7 | 8 | func fetchPrincipalId(testCtx *acctest.TestContext, name string) string { 9 | var id string 10 | err := testCtx.GetMasterDBConnection(). 11 | QueryRow(fmt.Sprintf("SELECT [principal_id] FROM sys.server_principals WHERE [name]=%s", name)).Scan(&id) 12 | testCtx.Require.NoError(err, "Fetching IDs") 13 | 14 | return id 15 | } 16 | -------------------------------------------------------------------------------- /internal/services/serverRole/data_acctest.go: -------------------------------------------------------------------------------- 1 | package serverRole 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | ) 8 | 9 | func testDataSource(testCtx *acctest.TestContext) { 10 | if testCtx.IsAzureTest { 11 | return 12 | } 13 | 14 | testCtx.ExecMasterDB("CREATE SERVER ROLE [test_owner]") 15 | defer testCtx.ExecMasterDB("DROP SERVER ROLE [test_owner]") 16 | 17 | testCtx.ExecMasterDB("CREATE SERVER ROLE [test_role_data] AUTHORIZATION [test_owner]") 18 | defer testCtx.ExecMasterDB("DROP SERVER ROLE [test_role_data]") 19 | 20 | testCtx.ExecMasterDB("CREATE SERVER ROLE [test_role_member]") 21 | testCtx.ExecMasterDB("ALTER SERVER ROLE [test_role_data] ADD MEMBER [test_role_member]") 22 | defer testCtx.ExecMasterDB("DROP SERVER ROLE [test_role_member]") 23 | 24 | roleId := fetchPrincipalId(testCtx, "'test_role_data'") 25 | ownerId := fetchPrincipalId(testCtx, "'test_owner'") 26 | memberId := fetchPrincipalId(testCtx, "'test_role_member'") 27 | 28 | testCtx.Test(resource.TestCase{ 29 | Steps: []resource.TestStep{ 30 | { 31 | Config: ` 32 | data "mssql_server_role" "by_name" { 33 | name = "test_role_data" 34 | } 35 | `, 36 | Check: resource.ComposeAggregateTestCheckFunc( 37 | resource.TestCheckResourceAttr("data.mssql_server_role.by_name", "id", roleId), 38 | resource.TestCheckResourceAttr("data.mssql_server_role.by_name", "owner_id", ownerId), 39 | resource.TestCheckTypeSetElemNestedAttrs("data.mssql_server_role.by_name", "members.*", map[string]string{ 40 | "id": memberId, 41 | "name": "test_role_member", 42 | "type": "SERVER_ROLE", 43 | }), 44 | ), 45 | }, 46 | { 47 | Config: fmt.Sprintf(` 48 | data "mssql_server_role" "by_id" { 49 | id = %q 50 | } 51 | `, roleId), 52 | Check: resource.ComposeAggregateTestCheckFunc( 53 | resource.TestCheckResourceAttr("data.mssql_server_role.by_id", "name", "test_role_data"), 54 | resource.TestCheckResourceAttr("data.mssql_server_role.by_id", "owner_id", ownerId), 55 | resource.TestCheckTypeSetElemNestedAttrs("data.mssql_server_role.by_id", "members.*", map[string]string{ 56 | "id": memberId, 57 | "name": "test_role_member", 58 | "type": "SERVER_ROLE", 59 | }), 60 | ), 61 | }, 62 | }, 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /internal/services/serverRole/list.go: -------------------------------------------------------------------------------- 1 | package serverRole 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 8 | "github.com/hashicorp/terraform-plugin-framework/datasource/schema" 9 | "github.com/hashicorp/terraform-plugin-framework/types" 10 | ) 11 | 12 | type listDataSourceData struct { 13 | Id types.String `tfsdk:"id"` 14 | Roles []resourceData `tfsdk:"roles"` 15 | } 16 | 17 | type listDataSource struct{} 18 | 19 | func (l listDataSource) GetName() string { 20 | return "server_roles" 21 | } 22 | 23 | func (l listDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { 24 | resp.Schema.MarkdownDescription = "Obtains information about all roles defined in the server." 25 | resp.Schema.Attributes = map[string]schema.Attribute{ 26 | "id": schema.StringAttribute{ 27 | MarkdownDescription: "Only used internally in Terraform. Always set to `server_roles`.", 28 | Computed: true, 29 | }, 30 | "roles": schema.SetNestedAttribute{ 31 | MarkdownDescription: "Set of all roles found in the server", 32 | Computed: true, 33 | NestedObject: schema.NestedAttributeObject{ 34 | Attributes: map[string]schema.Attribute{ 35 | "id": schema.StringAttribute{ 36 | MarkdownDescription: attrDescriptions["id"], 37 | Computed: true, 38 | }, 39 | "name": schema.StringAttribute{ 40 | MarkdownDescription: attrDescriptions["name"], 41 | Computed: true, 42 | }, 43 | "owner_id": schema.StringAttribute{ 44 | MarkdownDescription: attrDescriptions["owner_id"], 45 | Computed: true, 46 | }, 47 | }, 48 | }, 49 | }, 50 | } 51 | } 52 | 53 | func (l listDataSource) Read(ctx context.Context, req datasource.ReadRequest[listDataSourceData], resp *datasource.ReadResponse[listDataSourceData]) { 54 | roles := sql.GetServerRoles(ctx, req.Conn) 55 | 56 | req. 57 | Then(func() { 58 | data := listDataSourceData{ 59 | Id: types.StringValue("server_roles"), 60 | Roles: []resourceData{}, 61 | } 62 | 63 | for id, role := range roles { 64 | roleData := resourceData{Id: types.StringValue(fmt.Sprint(id))}.withSettings(role.GetSettings(ctx)) 65 | data.Roles = append(data.Roles, roleData) 66 | } 67 | 68 | resp.SetState(data) 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /internal/services/serverRole/list_acctest.go: -------------------------------------------------------------------------------- 1 | package serverRole 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 6 | ) 7 | 8 | func testListDataSource(testCtx *acctest.TestContext) { 9 | if testCtx.IsAzureTest { 10 | return 11 | } 12 | 13 | testCtx.ExecMasterDB("CREATE SERVER ROLE [test_owner]") 14 | defer testCtx.ExecMasterDB("DROP SERVER ROLE [test_owner]") 15 | 16 | testCtx.ExecMasterDB("CREATE SERVER ROLE [test_list_role] AUTHORIZATION [test_owner]") 17 | defer testCtx.ExecMasterDB("DROP SERVER ROLE [test_list_role]") 18 | 19 | ownerId := fetchPrincipalId(testCtx, "'test_owner'") 20 | roleId := fetchPrincipalId(testCtx, "'test_list_role'") 21 | 22 | testCtx.Test(resource.TestCase{ 23 | Steps: []resource.TestStep{ 24 | { 25 | Config: `data "mssql_server_roles" "all" {}`, 26 | Check: resource.TestCheckTypeSetElemNestedAttrs("data.mssql_server_roles.all", "roles.*", map[string]string{ 27 | "id": roleId, 28 | "name": "test_list_role", 29 | "owner_id": ownerId, 30 | }), 31 | }, 32 | }, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /internal/services/serverRole/main.go: -------------------------------------------------------------------------------- 1 | package serverRole 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkresource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "server_role" 19 | } 20 | 21 | func (s service) Resources() []func() sdkresource.ResourceWithConfigure { 22 | return []func() sdkresource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[dataSourceData](&dataSource{}), 30 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 31 | } 32 | } 33 | 34 | func (s service) Tests() core.AccTests { 35 | return core.AccTests{ 36 | DataSource: testDataSource, 37 | ListDataSource: testListDataSource, 38 | Resource: testResource, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/services/serverRoleMember/main.go: -------------------------------------------------------------------------------- 1 | package serverRoleMember 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 6 | "github.com/hashicorp/terraform-plugin-framework/datasource" 7 | sdkResource "github.com/hashicorp/terraform-plugin-framework/resource" 8 | ) 9 | 10 | func Service() core.Service { 11 | return service{} 12 | } 13 | 14 | type service struct{} 15 | 16 | func (s service) Name() string { 17 | return "server_role_member" 18 | } 19 | 20 | func (s service) Resources() []func() sdkResource.ResourceWithConfigure { 21 | return []func() sdkResource.ResourceWithConfigure{ 22 | resource.NewResource[resourceData](&res{}), 23 | } 24 | } 25 | 26 | func (s service) DataSources() []func() datasource.DataSourceWithConfigure { 27 | return []func() datasource.DataSourceWithConfigure{} 28 | } 29 | 30 | func (s service) Tests() core.AccTests { 31 | return core.AccTests{ 32 | Resource: testResource, 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /internal/services/serverRoleMember/resource_acctest.go: -------------------------------------------------------------------------------- 1 | package serverRoleMember 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func testResource(testCtx *acctest.TestContext) { 11 | roleName := "##MS_ServerStateReader##" 12 | 13 | var memberId string 14 | 15 | if testCtx.IsAzureTest { 16 | testCtx.ExecMasterDB("CREATE LOGIN [%s] FROM EXTERNAL PROVIDER", testCtx.AzureADTestGroup.Name) 17 | defer testCtx.ExecMasterDB("DROP LOGIN [%s]", testCtx.AzureADTestGroup.Name) 18 | 19 | err := testCtx.GetMasterDBConnection().QueryRow("SELECT [principal_id] FROM sys.server_principals WHERE [type] IN ('X', 'E')").Scan(&memberId) 20 | testCtx.Require.NoError(err, "Fetching IDs") 21 | } else { 22 | testCtx.ExecMasterDB("CREATE SERVER ROLE [test_role_member]") 23 | defer testCtx.ExecMasterDB("DROP SERVER ROLE [test_role_member]") 24 | roleName = "test_role_member" 25 | 26 | testCtx.ExecMasterDB("CREATE SERVER ROLE [test_role_member_member]") 27 | defer testCtx.ExecDefaultDB("DROP SERVER ROLE [test_role_member_member]") 28 | err := testCtx.GetMasterDBConnection().QueryRow("SELECT [principal_id] FROM sys.server_principals WHERE [name] = 'test_role_member_member'").Scan(&memberId) 29 | testCtx.Require.NoError(err, "Fetching IDs") 30 | } 31 | 32 | var roleId string 33 | err := testCtx.GetMasterDBConnection().QueryRow("SELECT [principal_id] FROM sys.server_principals WHERE [name]=@p1", roleName).Scan(&roleId) 34 | testCtx.Require.NoError(err, "Fetching IDs") 35 | 36 | resourceId := fmt.Sprintf("%s/%s", roleId, memberId) 37 | 38 | config := fmt.Sprintf(` 39 | resource "mssql_server_role_member" "test" { 40 | role_id = %[1]q 41 | member_id = %[2]q 42 | } 43 | `, roleId, memberId) 44 | 45 | testCtx.Test(resource.TestCase{ 46 | Steps: []resource.TestStep{ 47 | { 48 | Config: config, 49 | Check: resource.ComposeAggregateTestCheckFunc( 50 | testCtx.SqlCheckMaster(func(conn *sql.DB) error { 51 | return conn.QueryRow("SELECT 1 FROM sys.server_role_members WHERE [role_principal_id] = @p1 AND [member_principal_id] = @p2", roleId, memberId).Err() 52 | }), 53 | resource.TestCheckResourceAttr("mssql_server_role_member.test", "id", resourceId), 54 | ), 55 | }, 56 | { 57 | ImportState: true, 58 | ImportStatePersist: false, 59 | ImportStateVerify: true, 60 | ImportStateId: resourceId, 61 | ResourceName: "mssql_server_role_member.test", 62 | Config: config, 63 | PlanOnly: true, 64 | }, 65 | }, 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /internal/services/sqlLogin/base.go: -------------------------------------------------------------------------------- 1 | package sqlLogin 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 7 | "github.com/hashicorp/terraform-plugin-framework/types" 8 | ) 9 | 10 | var attrDescriptions = map[string]string{ 11 | "id": "Login SID. Can be retrieved using `SELECT SUSER_SID('')`.", 12 | "name": fmt.Sprintf("Login name. %s and cannot contain `\\ `", common.RegularIdentifiersDoc), 13 | "must_change_password": "When true, password change will be forced on first logon.", 14 | "default_database_id": "ID of login's default DB. The ID can be retrieved using `mssql_database` data resource.", 15 | "default_language": "Default language assigned to login.", 16 | "check_password_expiration": "When `true`, password expiration policy is enforced for this login.", 17 | "check_password_policy": "When `true`, the Windows password policies of the computer on which SQL Server is running are enforced on this login.", 18 | "principal_id": "ID used to reference SQL Login in other resources, e.g. `server_role`. Can be retrieved from `sys.sql_logins`.", 19 | } 20 | 21 | type dataSourceData struct { 22 | Id types.String `tfsdk:"id"` 23 | Name types.String `tfsdk:"name"` 24 | MustChangePassword types.Bool `tfsdk:"must_change_password"` 25 | DefaultDatabaseId types.String `tfsdk:"default_database_id"` 26 | DefaultLanguage types.String `tfsdk:"default_language"` 27 | CheckPasswordExpiration types.Bool `tfsdk:"check_password_expiration"` 28 | CheckPasswordPolicy types.Bool `tfsdk:"check_password_policy"` 29 | PrincipalId types.String `tfsdk:"principal_id"` 30 | } 31 | 32 | func (d dataSourceData) withSettings(settings sql.SqlLoginSettings) dataSourceData { 33 | return dataSourceData{ 34 | Id: d.Id, 35 | Name: types.StringValue(settings.Name), 36 | MustChangePassword: types.BoolValue(settings.MustChangePassword), 37 | DefaultDatabaseId: types.StringValue(fmt.Sprint(settings.DefaultDatabaseId)), 38 | DefaultLanguage: types.StringValue(settings.DefaultLanguage), 39 | CheckPasswordExpiration: types.BoolValue(settings.CheckPasswordExpiration), 40 | CheckPasswordPolicy: types.BoolValue(settings.CheckPasswordPolicy), 41 | PrincipalId: types.StringValue(fmt.Sprint(settings.PrincipalId)), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/services/sqlLogin/data.go: -------------------------------------------------------------------------------- 1 | package sqlLogin 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 7 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 8 | "github.com/hashicorp/terraform-plugin-framework/datasource/schema" 9 | 10 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 11 | "github.com/hashicorp/terraform-plugin-framework/types" 12 | ) 13 | 14 | type dataSource struct{} 15 | 16 | func (d *dataSource) GetName() string { 17 | return "sql_login" 18 | } 19 | 20 | func (d *dataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { 21 | resp.Schema.MarkdownDescription = "Obtains information about single SQL login." 22 | resp.Schema.Attributes = map[string]schema.Attribute{ 23 | "id": schema.StringAttribute{ 24 | MarkdownDescription: attrDescriptions["id"], 25 | Computed: true, 26 | }, 27 | "name": schema.StringAttribute{ 28 | MarkdownDescription: attrDescriptions["name"], 29 | Required: true, 30 | }, 31 | "must_change_password": schema.BoolAttribute{ 32 | MarkdownDescription: attrDescriptions["must_change_password"], 33 | Computed: true, 34 | }, 35 | "default_database_id": schema.StringAttribute{ 36 | MarkdownDescription: attrDescriptions["default_database_id"], 37 | Computed: true, 38 | }, 39 | "default_language": schema.StringAttribute{ 40 | MarkdownDescription: attrDescriptions["default_language"], 41 | Computed: true, 42 | }, 43 | "check_password_expiration": schema.BoolAttribute{ 44 | MarkdownDescription: attrDescriptions["check_password_expiration"], 45 | Computed: true, 46 | }, 47 | "check_password_policy": schema.BoolAttribute{ 48 | MarkdownDescription: attrDescriptions["check_password_policy"], 49 | Computed: true, 50 | }, 51 | "principal_id": schema.StringAttribute{ 52 | MarkdownDescription: attrDescriptions["principal_id"], 53 | Computed: true, 54 | }, 55 | } 56 | } 57 | 58 | func (d *dataSource) Read(ctx context.Context, req datasource.ReadRequest[dataSourceData], resp *datasource.ReadResponse[dataSourceData]) { 59 | var login sql.SqlLogin 60 | 61 | req. 62 | Then(func() { 63 | login = sql.GetSqlLoginByName(ctx, req.Conn, req.Config.Name.ValueString()) 64 | 65 | if login == nil || !login.Exists(ctx) { 66 | utils.AddError(ctx, "Login does not exist", fmt.Errorf("could not find SQL Login '%s'", req.Config.Name.ValueString())) 67 | } 68 | }). 69 | Then(func() { 70 | state := req.Config.withSettings(login.GetSettings(ctx)) 71 | state.Id = types.StringValue(fmt.Sprint(login.GetId(ctx))) 72 | 73 | resp.SetState(state) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /internal/services/sqlLogin/data_acctest.go: -------------------------------------------------------------------------------- 1 | package sqlLogin 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "regexp" 9 | ) 10 | 11 | func testDataSource(testCtx *acctest.TestContext) { 12 | 13 | newDataResource := func(resourceName string, loginName string) string { 14 | return fmt.Sprintf(` 15 | data "mssql_sql_login" %[1]q { 16 | name = %[2]q 17 | } 18 | `, resourceName, loginName) 19 | } 20 | 21 | var loginId, principalId string 22 | 23 | defer testCtx.ExecMasterDB("DROP LOGIN [test_login]") 24 | 25 | testCtx.Test(resource.TestCase{ 26 | Steps: []resource.TestStep{ 27 | { 28 | Config: newDataResource("not_exists", "not_exists"), 29 | ExpectError: regexp.MustCompile("not exist"), 30 | }, 31 | { 32 | PreConfig: func() { 33 | conn := testCtx.GetMasterDBConnection() 34 | loginOptions := fmt.Sprintf(" MUST_CHANGE, CHECK_EXPIRATION=ON, CHECK_POLICY=ON, DEFAULT_LANGUAGE=[polish], DEFAULT_DATABASE=[%s]", acctest.DefaultDbName) 35 | if testCtx.IsAzureTest { 36 | loginOptions = "" 37 | } 38 | _, err := conn.Exec("CREATE LOGIN [test_login] WITH PASSWORD='C0mplicatedPa$$w0rd123'" + loginOptions) 39 | testCtx.Require.NoError(err, "creating login") 40 | 41 | err = conn.QueryRow("SELECT CONVERT(VARCHAR(85), [sid], 1), [principal_id] FROM sys.sql_logins WHERE [name] = 'test_login'").Scan(&loginId, &principalId) 42 | testCtx.Require.NoError(err, "fetching IDs") 43 | }, 44 | Config: newDataResource("exists", "test_login"), 45 | Check: resource.ComposeAggregateTestCheckFunc( 46 | resource.TestCheckResourceAttrPtr("data.mssql_sql_login.exists", "id", &loginId), 47 | resource.TestCheckResourceAttr("data.mssql_sql_login.exists", "name", "test_login"), 48 | resource.TestCheckResourceAttrPtr("data.mssql_sql_login.exists", "principal_id", &principalId), 49 | func(state *terraform.State) error { 50 | if testCtx.IsAzureTest { 51 | return nil 52 | } 53 | 54 | return resource.ComposeAggregateTestCheckFunc( 55 | resource.TestCheckResourceAttr("data.mssql_sql_login.exists", "must_change_password", "true"), 56 | resource.TestCheckResourceAttr("data.mssql_sql_login.exists", "default_database_id", fmt.Sprint(testCtx.DefaultDBId)), 57 | resource.TestCheckResourceAttr("data.mssql_sql_login.exists", "default_language", "polish"), 58 | resource.TestCheckResourceAttr("data.mssql_sql_login.exists", "check_password_expiration", "true"), 59 | resource.TestCheckResourceAttr("data.mssql_sql_login.exists", "check_password_policy", "true"), 60 | )(state) 61 | }, 62 | ), 63 | }, 64 | }, 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /internal/services/sqlLogin/list_acctest.go: -------------------------------------------------------------------------------- 1 | package sqlLogin 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 7 | ) 8 | 9 | func testListDataSource(testCtx *acctest.TestContext) { 10 | var loginId, principalId string 11 | 12 | defer testCtx.ExecMasterDB("DROP LOGIN [test_sql_login_list]") 13 | 14 | testCtx.Test(resource.TestCase{ 15 | PreCheck: func() { 16 | conn := testCtx.GetMasterDBConnection() 17 | err := conn.QueryRow(` 18 | CREATE LOGIN [test_sql_login_list] WITH PASSWORD='Str0ngPa$$w0rd124'; 19 | SELECT CONVERT(VARCHAR(85), [sid], 1), [principal_id] FROM sys.sql_logins WHERE [name]='test_sql_login_list' 20 | `).Scan(&loginId, &principalId) 21 | 22 | testCtx.Require.NoError(err, "creating login") 23 | }, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: `data "mssql_sql_logins" "list" {}`, 27 | Check: func(state *terraform.State) error { 28 | expectedAttributes := map[string]string{ 29 | "id": loginId, 30 | "name": "test_sql_login_list", 31 | "principal_id": principalId, 32 | "must_change_password": "false", 33 | "default_database_id": "1", 34 | "default_language": "us_english", 35 | "check_password_expiration": "false", 36 | "check_password_policy": "true", 37 | } 38 | 39 | if testCtx.IsAzureTest { 40 | expectedAttributes["check_password_policy"] = "false" 41 | } 42 | 43 | return resource.TestCheckTypeSetElemNestedAttrs("data.mssql_sql_logins.list", "logins.*", expectedAttributes)(state) 44 | }, 45 | }, 46 | }, 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /internal/services/sqlLogin/main.go: -------------------------------------------------------------------------------- 1 | package sqlLogin 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkResource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "sql_login" 19 | } 20 | 21 | func (s service) Resources() []func() sdkResource.ResourceWithConfigure { 22 | return []func() sdkResource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[dataSourceData](&dataSource{}), 30 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 31 | } 32 | } 33 | 34 | func (s service) Tests() core.AccTests { 35 | return core.AccTests{ 36 | Resource: testResource, 37 | DataSource: testDataSource, 38 | ListDataSource: testListDataSource, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/services/sqlUser/base.go: -------------------------------------------------------------------------------- 1 | package sqlUser 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 6 | "github.com/hashicorp/terraform-plugin-framework/types" 7 | ) 8 | 9 | var attrDescriptions = map[string]string{ 10 | "id": "`/`. User ID can be retrieved using `SELECT DATABASE_PRINCIPAL_ID('')`.", 11 | "name": "User name. Cannot be longer than 128 chars.", 12 | "login_id": "SID of SQL login. Can be retrieved using `mssql_sql_login` or `SELECT SUSER_SID('')`.", 13 | } 14 | 15 | type resourceData struct { 16 | Id types.String `tfsdk:"id"` 17 | Name types.String `tfsdk:"name"` 18 | DatabaseId types.String `tfsdk:"database_id"` 19 | LoginId types.String `tfsdk:"login_id"` 20 | } 21 | 22 | func (d resourceData) toSettings() sql.UserSettings { 23 | return sql.UserSettings{ 24 | Name: d.Name.ValueString(), 25 | LoginId: sql.LoginId(d.LoginId.ValueString()), 26 | Type: sql.USER_TYPE_SQL, 27 | } 28 | } 29 | 30 | func (d resourceData) withSettings(settings sql.UserSettings) resourceData { 31 | return resourceData{ 32 | Id: d.Id, 33 | DatabaseId: d.DatabaseId, 34 | Name: types.StringValue(settings.Name), 35 | LoginId: types.StringValue(fmt.Sprint(settings.LoginId)), 36 | } 37 | } 38 | 39 | func (d resourceData) withIds(dbId sql.DatabaseId, userId sql.UserId) resourceData { 40 | return resourceData{ 41 | Id: types.StringValue(fmt.Sprintf("%v/%v", dbId, userId)), 42 | DatabaseId: types.StringValue(fmt.Sprint(dbId)), 43 | Name: d.Name, 44 | LoginId: d.LoginId, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /internal/services/sqlUser/data.go: -------------------------------------------------------------------------------- 1 | package sqlUser 2 | 3 | import ( 4 | "context" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | common2 "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 7 | "github.com/hashicorp/terraform-plugin-framework/datasource/schema" 8 | 9 | "github.com/PGSSoft/terraform-provider-mssql/internal/sql" 10 | ) 11 | 12 | type dataSource struct{} 13 | 14 | func (d *dataSource) GetName() string { 15 | return "sql_user" 16 | } 17 | 18 | func (d *dataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { 19 | resp.Schema.MarkdownDescription = "Obtains information about single SQL database user." 20 | resp.Schema.Attributes = map[string]schema.Attribute{ 21 | "id": schema.StringAttribute{ 22 | MarkdownDescription: attrDescriptions["id"], 23 | Computed: true, 24 | }, 25 | "name": schema.StringAttribute{ 26 | MarkdownDescription: attrDescriptions["name"], 27 | Required: true, 28 | }, 29 | "database_id": schema.StringAttribute{ 30 | MarkdownDescription: common2.AttributeDescriptions["database_id"], 31 | Optional: true, 32 | Computed: true, 33 | }, 34 | "login_id": schema.StringAttribute{ 35 | MarkdownDescription: attrDescriptions["login_id"], 36 | Computed: true, 37 | }, 38 | } 39 | } 40 | 41 | func (d *dataSource) Read(ctx context.Context, req datasource.ReadRequest[resourceData], resp *datasource.ReadResponse[resourceData]) { 42 | var db sql.Database 43 | var user sql.User 44 | 45 | req. 46 | Then(func() { db = common2.GetResourceDb(ctx, req.Conn, req.Config.DatabaseId.ValueString()) }). 47 | Then(func() { user = sql.GetUserByName(ctx, db, req.Config.Name.ValueString()) }). 48 | Then(func() { 49 | state := req.Config.withIds(db.GetId(ctx), user.GetId(ctx)) 50 | resp.SetState(state.withSettings(user.GetSettings(ctx))) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /internal/services/sqlUser/list_acctest.go: -------------------------------------------------------------------------------- 1 | package sqlUser 2 | 3 | import ( 4 | "fmt" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | ) 9 | 10 | func testListDataSource(testCtx *acctest.TestContext) { 11 | var userId, loginId, resourceId string 12 | 13 | defer testCtx.ExecMasterDB("DROP LOGIN [sql_users_list_test]") 14 | defer testCtx.ExecDefaultDB("DROP USER [sql_users_list_test]") 15 | 16 | testCtx.Test(resource.TestCase{ 17 | Steps: []resource.TestStep{ 18 | { 19 | Config: `data "mssql_sql_users" "master" {}`, 20 | Check: resource.TestCheckTypeSetElemNestedAttrs("data.mssql_sql_users.master", "users.*", map[string]string{ 21 | "id": "1/1", 22 | "name": "dbo", 23 | "database_id": "1", 24 | "login_id": "0x01", 25 | }), 26 | }, 27 | { 28 | PreConfig: func() { 29 | master := testCtx.GetMasterDBConnection() 30 | err := master.QueryRow(` 31 | CREATE LOGIN [sql_users_list_test] WITH PASSWORD='C0mplicatedPa$$w0rd123'; 32 | SELECT CONVERT(VARCHAR(85), [sid], 1) FROM sys.sql_logins WHERE [name] = 'sql_users_list_test' 33 | `).Scan(&loginId) 34 | 35 | testCtx.Require.NoError(err, "creating login") 36 | 37 | defaultDB := testCtx.GetDefaultDBConnection() 38 | err = defaultDB.QueryRow(` 39 | CREATE USER [sql_users_list_test] FOR LOGIN [sql_users_list_test]; 40 | SELECT DATABASE_PRINCIPAL_ID('sql_users_list_test'); 41 | `).Scan(&userId) 42 | 43 | testCtx.Require.NoError(err, "creating user") 44 | 45 | resourceId = fmt.Sprintf("%d/%s", testCtx.DefaultDBId, userId) 46 | }, 47 | Config: fmt.Sprintf(` 48 | data "mssql_sql_users" "test" { 49 | database_id = %[1]d 50 | } 51 | `, testCtx.DefaultDBId), 52 | Check: func(state *terraform.State) error { 53 | return resource.TestCheckTypeSetElemNestedAttrs("data.mssql_sql_users.test", "users.*", map[string]string{ 54 | "id": resourceId, 55 | "name": "sql_users_list_test", 56 | "database_id": fmt.Sprint(testCtx.DefaultDBId), 57 | "login_id": loginId, 58 | })(state) 59 | }, 60 | }, 61 | }, 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /internal/services/sqlUser/main.go: -------------------------------------------------------------------------------- 1 | package sqlUser 2 | 3 | import ( 4 | "github.com/PGSSoft/terraform-provider-mssql/internal/core" 5 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/datasource" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/core/resource" 7 | sdkdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" 8 | sdkResource "github.com/hashicorp/terraform-plugin-framework/resource" 9 | ) 10 | 11 | func Service() core.Service { 12 | return service{} 13 | } 14 | 15 | type service struct{} 16 | 17 | func (s service) Name() string { 18 | return "sql_user" 19 | } 20 | 21 | func (s service) Resources() []func() sdkResource.ResourceWithConfigure { 22 | return []func() sdkResource.ResourceWithConfigure{ 23 | resource.NewResource[resourceData](&res{}), 24 | } 25 | } 26 | 27 | func (s service) DataSources() []func() sdkdatasource.DataSourceWithConfigure { 28 | return []func() sdkdatasource.DataSourceWithConfigure{ 29 | datasource.NewDataSource[resourceData](&dataSource{}), 30 | datasource.NewDataSource[listDataSourceData](&listDataSource{}), 31 | } 32 | } 33 | 34 | func (s service) Tests() core.AccTests { 35 | return core.AccTests{ 36 | Resource: testResource, 37 | DataSource: testDataSource, 38 | ListDataSource: testListDataSource, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/sql/azureAuth.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/microsoft/go-mssqldb/azuread" 7 | "github.com/hashicorp/terraform-plugin-framework/diag" 8 | "net/url" 9 | ) 10 | 11 | type ConnectionAuthAzure struct { 12 | ClientId string 13 | ClientSecret string 14 | TenantId string 15 | } 16 | 17 | func (auth ConnectionAuthAzure) configure(_ context.Context, u *url.URL) diag.Diagnostics { 18 | q := u.Query() 19 | 20 | if auth.ClientId == "" || auth.ClientSecret == "" { 21 | q.Set("fedauth", "ActiveDirectoryDefault") 22 | u.RawQuery = q.Encode() 23 | return nil 24 | } 25 | 26 | q.Set("fedauth", "ActiveDirectoryServicePrincipal") 27 | q.Set("password", auth.ClientSecret) 28 | 29 | if auth.TenantId != "" { 30 | q.Set("user id", fmt.Sprintf("%s@%s", auth.ClientId, auth.TenantId)) 31 | } else { 32 | q.Set("user id", auth.ClientId) 33 | } 34 | 35 | u.RawQuery = q.Encode() 36 | return nil 37 | } 38 | 39 | func (ConnectionAuthAzure) getDriverName() string { 40 | return azuread.DriverName 41 | } 42 | -------------------------------------------------------------------------------- /internal/sql/azureAuth_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/stretchr/testify/assert" 7 | "net/url" 8 | "testing" 9 | ) 10 | 11 | func TestConfigureDefault(t *testing.T) { 12 | u := url.URL{} 13 | auth := ConnectionAuthAzure{} 14 | 15 | auth.configure(context.Background(), &u) 16 | 17 | assert.Equal(t, "ActiveDirectoryDefault", u.Query().Get("fedauth")) 18 | } 19 | 20 | func TestConfigureServicePrincipal(t *testing.T) { 21 | u := url.URL{} 22 | auth := ConnectionAuthAzure{ 23 | ClientId: "test_client_id", 24 | ClientSecret: "test_client_secret", 25 | } 26 | 27 | auth.configure(context.Background(), &u) 28 | 29 | assert.Equal(t, "ActiveDirectoryServicePrincipal", u.Query().Get("fedauth")) 30 | assert.Equal(t, auth.ClientId, u.Query().Get("user id"), "user id") 31 | assert.Equal(t, auth.ClientSecret, u.Query().Get("password"), "password") 32 | } 33 | 34 | func TestConfigureServicePrincipalWithTenant(t *testing.T) { 35 | u := url.URL{} 36 | auth := ConnectionAuthAzure{ 37 | ClientId: "test_client_id", 38 | ClientSecret: "test_client_secret", 39 | TenantId: "test_tenant_id", 40 | } 41 | 42 | auth.configure(context.Background(), &u) 43 | 44 | assert.Equal(t, fmt.Sprintf("%s@%s", auth.ClientId, auth.TenantId), u.Query().Get("user id")) 45 | } 46 | -------------------------------------------------------------------------------- /internal/sql/connectionMock_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 7 | "github.com/stretchr/testify/mock" 8 | ) 9 | 10 | type SQLEdition string 11 | 12 | const ( 13 | EDITION_AZURE_SQL SQLEdition = "SQL Azure" 14 | EDITION_ENTERPRISE SQLEdition = "Enterprise Edition" 15 | ) 16 | 17 | var _ Connection = &connectionMock{} 18 | 19 | type connectionMock struct { 20 | mock.Mock 21 | db *sql.DB 22 | edition SQLEdition 23 | } 24 | 25 | func (c *connectionMock) IsAzure(ctx context.Context) bool { 26 | return c.edition == EDITION_AZURE_SQL 27 | } 28 | 29 | func (c *connectionMock) GetPermissions(ctx context.Context, principalId GenericServerPrincipalId) ServerPermissions { 30 | return c.Called(ctx, principalId).Get(0).(ServerPermissions) 31 | } 32 | 33 | func (c *connectionMock) GrantPermission(ctx context.Context, principalId GenericServerPrincipalId, permission ServerPermission) { 34 | c.Called(ctx, principalId, permission) 35 | } 36 | 37 | func (c *connectionMock) RevokePermission(ctx context.Context, principalId GenericServerPrincipalId, permission string) { 38 | c.Called(ctx, principalId, permission) 39 | } 40 | 41 | func (c *connectionMock) exec(ctx context.Context, query string, args ...any) sql.Result { 42 | res, err := c.db.ExecContext(ctx, query, args...) 43 | if err != nil { 44 | utils.AddError(ctx, "mock error", err) 45 | } 46 | return res 47 | } 48 | 49 | func (c *connectionMock) getConnectionDetails(ctx context.Context) ConnectionDetails { 50 | return c.Called(ctx).Get(0).(ConnectionDetails) 51 | } 52 | 53 | func (c *connectionMock) getSqlConnection(ctx context.Context) *sql.DB { 54 | return c.db 55 | } 56 | 57 | func (c *connectionMock) getDBSqlConnection(ctx context.Context, dbName string) *sql.DB { 58 | return c.db 59 | } 60 | 61 | func (c *connectionMock) lookupServerPrincipalName(ctx context.Context, id GenericServerPrincipalId) string { 62 | return c.Called(ctx, id).String(0) 63 | } 64 | 65 | func (c *connectionMock) lookupServerPrincipalId(ctx context.Context, name string) GenericServerPrincipalId { 66 | return c.Called(ctx, name).Get(0).(GenericServerPrincipalId) 67 | } 68 | -------------------------------------------------------------------------------- /internal/sql/dbMock_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "github.com/stretchr/testify/mock" 7 | ) 8 | 9 | var _ Database = &dbMock{} 10 | 11 | type dbMock struct { 12 | mock.Mock 13 | } 14 | 15 | func (m *dbMock) GetConnection(ctx context.Context) Connection { 16 | return m.Called(ctx).Get(0).(Connection) 17 | } 18 | 19 | func (m *dbMock) GetId(ctx context.Context) DatabaseId { 20 | return m.Called(ctx).Get(0).(DatabaseId) 21 | } 22 | 23 | func (m *dbMock) Exists(ctx context.Context) bool { 24 | return m.Called(ctx).Bool(0) 25 | } 26 | 27 | func (m *dbMock) GetSettings(ctx context.Context) DatabaseSettings { 28 | return m.Called(ctx).Get(0).(DatabaseSettings) 29 | } 30 | 31 | func (m *dbMock) Rename(ctx context.Context, name string) { 32 | m.Called(ctx, name) 33 | } 34 | 35 | func (m *dbMock) SetCollation(ctx context.Context, collation string) { 36 | m.Called(ctx, collation) 37 | } 38 | 39 | func (m *dbMock) Drop(ctx context.Context) { 40 | m.Called(ctx) 41 | } 42 | 43 | func (m *dbMock) CreateUser(ctx context.Context, settings UserSettings) User { 44 | return m.Called(ctx, settings).Get(0).(User) 45 | } 46 | 47 | func (m *dbMock) GetUser(ctx context.Context, id UserId) User { 48 | return m.Called(ctx, id).Get(0).(User) 49 | } 50 | 51 | func (m *dbMock) Query(ctx context.Context, query string) []map[string]string { 52 | return m.Called(ctx, query).Get(0).([]map[string]string) 53 | } 54 | 55 | func (m *dbMock) Exec(ctx context.Context, script string) { 56 | m.Called(ctx, script) 57 | } 58 | 59 | func (m *dbMock) GetPermissions(ctx context.Context, id GenericDatabasePrincipalId) DatabasePermissions { 60 | return m.Called(ctx, id).Get(0).(DatabasePermissions) 61 | } 62 | 63 | func (m *dbMock) GrantPermission(ctx context.Context, id GenericDatabasePrincipalId, permission DatabasePermission) { 64 | m.Called(ctx, id, permission) 65 | } 66 | 67 | func (m *dbMock) UpdatePermission(ctx context.Context, id GenericDatabasePrincipalId, permission DatabasePermission) { 68 | m.Called(ctx, id, permission) 69 | } 70 | 71 | func (m *dbMock) RevokePermission(ctx context.Context, id GenericDatabasePrincipalId, permissionName string) { 72 | m.Called(ctx, id, permissionName) 73 | } 74 | 75 | func (m *dbMock) connect(ctx context.Context) *sql.DB { 76 | return m.Called(ctx).Get(0).(*sql.DB) 77 | } 78 | 79 | func (m *dbMock) getUserName(ctx context.Context, id GenericDatabasePrincipalId) string { 80 | return m.Called(ctx, id).String(0) 81 | } 82 | 83 | func (m *dbMock) expectUsernameLookup(userId int, userName string) { 84 | m.On("getUserName", mock.Anything, GenericDatabasePrincipalId(userId)).Return(userName) 85 | } 86 | -------------------------------------------------------------------------------- /internal/sql/ids.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | type DatabaseId int 4 | 5 | type GenericDatabasePrincipalId int 6 | 7 | type UserId GenericDatabasePrincipalId 8 | 9 | type DatabaseRoleId GenericDatabasePrincipalId 10 | 11 | const EmptyDatabasePrincipalId GenericDatabasePrincipalId = -1 12 | 13 | type LoginId string 14 | 15 | type AADObjectId string 16 | 17 | type SchemaId int 18 | 19 | type DatabasePrincipalId interface { 20 | UserId | DatabaseRoleId | GenericDatabasePrincipalId 21 | } 22 | 23 | type GenericServerPrincipalId int 24 | 25 | type ServerRoleId GenericServerPrincipalId 26 | 27 | type SqlLoginId GenericServerPrincipalId 28 | 29 | const EmptyServerPrincipalId GenericServerPrincipalId = -1 30 | 31 | type NumericObjectId interface { 32 | DatabaseId | DatabasePrincipalId | SchemaId | GenericServerPrincipalId 33 | } 34 | 35 | type StringObjectId interface { 36 | LoginId 37 | } 38 | 39 | type ObjectId interface { 40 | NumericObjectId | StringObjectId 41 | } 42 | 43 | type DatabasePrincipalType int 44 | 45 | const ( 46 | UNONOWN DatabasePrincipalType = iota 47 | SQL_USER 48 | DATABASE_ROLE 49 | AZUREAD_USER 50 | ) 51 | 52 | type ServerPrincipalType int 53 | 54 | const ( 55 | UNKNOWN ServerPrincipalType = iota 56 | SQL_LOGIN 57 | SERVER_ROLE 58 | ) 59 | -------------------------------------------------------------------------------- /internal/sql/sqlAuth.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-framework/diag" 6 | "net/url" 7 | ) 8 | 9 | type ConnectionAuthSql struct { 10 | Username string 11 | Password string 12 | } 13 | 14 | func (auth ConnectionAuthSql) configure(_ context.Context, u *url.URL) diag.Diagnostics { 15 | u.User = url.UserPassword(auth.Username, auth.Password) 16 | return nil 17 | } 18 | 19 | func (ConnectionAuthSql) getDriverName() string { 20 | return "sqlserver" 21 | } 22 | -------------------------------------------------------------------------------- /internal/sql/sqlAuth_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/stretchr/testify/assert" 7 | "net/url" 8 | "testing" 9 | ) 10 | 11 | func TestConfigure(t *testing.T) { 12 | var u url.URL 13 | auth := ConnectionAuthSql{Username: "test_username", Password: "test_password"} 14 | 15 | auth.configure(context.Background(), &u) 16 | 17 | assert.Equal(t, fmt.Sprintf("%s:%s", auth.Username, auth.Password), u.User.String()) 18 | } 19 | -------------------------------------------------------------------------------- /internal/sql/utils.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/utils" 7 | ) 8 | 9 | func WithConnection[T any](ctx context.Context, connectionFactory func(context.Context) *sql.DB, action func(*sql.DB) T) T { 10 | conn := connectionFactory(ctx) 11 | if utils.HasError(ctx) { 12 | var result T 13 | return result 14 | } 15 | 16 | return action(conn) 17 | } 18 | 19 | func getPrincipalName[T DatabasePrincipalId](ctx context.Context, conn *sql.DB, id T) string { 20 | var res string 21 | 22 | if err := conn.QueryRowContext(ctx, "SELECT USER_NAME(@p1)", id).Scan(&res); err != nil { 23 | utils.AddError(ctx, "Failed to retrieve DB principal name", err) 24 | } 25 | 26 | return res 27 | } 28 | 29 | func getCurrentUserName(ctx context.Context, conn *sql.DB) string { 30 | var res string 31 | 32 | if err := conn.QueryRowContext(ctx, "SELECT USER_NAME()").Scan(&res); err != nil { 33 | utils.AddError(ctx, "Failed to retrieve current user name", err) 34 | } 35 | 36 | return res 37 | } 38 | -------------------------------------------------------------------------------- /internal/sql/utils_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "fmt" 5 | "github.com/DATA-DOG/go-sqlmock" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | type SqlMock interface { 11 | ExpectQuery(query string) *sqlmock.ExpectedQuery 12 | ExpectExec(query string) *sqlmock.ExpectedExec 13 | } 14 | 15 | func expectExactQuery(mock SqlMock, queryFmt string, fmtArgs ...any) *sqlmock.ExpectedQuery { 16 | return mock.ExpectQuery(formatExactSql(queryFmt, fmtArgs)) 17 | } 18 | 19 | func expectExactExec(mock SqlMock, execFmt string, fmtArgs ...any) *sqlmock.ExpectedExec { 20 | return mock.ExpectExec(formatExactSql(execFmt, fmtArgs)) 21 | } 22 | 23 | func newRows(cols ...string) *sqlmock.Rows { 24 | return sqlmock.NewRows(cols) 25 | } 26 | 27 | func formatExactSql(fmtSql string, args []any) string { 28 | return fmt.Sprintf("^%s$", regexp.QuoteMeta(strings.TrimSpace(fmt.Sprintf(fmtSql, args...)))) 29 | } 30 | -------------------------------------------------------------------------------- /internal/utils/diagnostics.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-framework/diag" 6 | "github.com/hashicorp/terraform-plugin-framework/path" 7 | ) 8 | 9 | const mssqlProviderUtilsDiagnosticsKey = "terraform.mssql.utils.diagnostics" 10 | 11 | func GetDiagnostics(ctx context.Context) *diag.Diagnostics { 12 | return ctx.Value(mssqlProviderUtilsDiagnosticsKey).(*diag.Diagnostics) 13 | } 14 | 15 | func WithDiagnostics(ctx context.Context, diagnostics *diag.Diagnostics) context.Context { 16 | return context.WithValue(ctx, mssqlProviderUtilsDiagnosticsKey, diagnostics) 17 | } 18 | 19 | func HasError(ctx context.Context) bool { 20 | return GetDiagnostics(ctx).HasError() 21 | } 22 | 23 | func AddError(ctx context.Context, summary string, err error) { 24 | if err != nil { 25 | GetDiagnostics(ctx).AddError(summary, err.Error()) 26 | } 27 | } 28 | 29 | func AddAttributeError(ctx context.Context, path path.Path, summary string, details string) { 30 | GetDiagnostics(ctx).AddAttributeError(path, summary, details) 31 | } 32 | 33 | func AppendDiagnostics(ctx context.Context, diagnostics ...diag.Diagnostic) { 34 | GetDiagnostics(ctx).Append(diagnostics...) 35 | } 36 | -------------------------------------------------------------------------------- /internal/utils/errorHandling.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "context" 4 | 5 | type ErrorMonad interface { 6 | Then(func()) ErrorMonad 7 | } 8 | 9 | type errorMonad struct { 10 | ctx context.Context 11 | } 12 | 13 | func (m errorMonad) Then(fn func()) ErrorMonad { 14 | if !HasError(m.ctx) { 15 | fn() 16 | } 17 | return m 18 | } 19 | 20 | func StopOnError(ctx context.Context) ErrorMonad { 21 | return errorMonad{ctx: ctx} 22 | } 23 | -------------------------------------------------------------------------------- /internal/utils/terraformDataBind.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-framework/diag" 6 | ) 7 | 8 | type DataGetter interface { 9 | Get(context.Context, any) diag.Diagnostics 10 | } 11 | 12 | func GetData[T any](ctx context.Context, dg DataGetter) (data T) { 13 | var d T 14 | diags := dg.Get(ctx, &d) 15 | AppendDiagnostics(ctx, diags...) 16 | return d 17 | } 18 | 19 | type DataSetter interface { 20 | Set(context.Context, any) diag.Diagnostics 21 | } 22 | 23 | func SetData(ctx context.Context, ds DataSetter, data any) { 24 | diags := ds.Set(ctx, data) 25 | AppendDiagnostics(ctx, diags...) 26 | } 27 | -------------------------------------------------------------------------------- /internal/validators/compositeValidators.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/schema/validator" 5 | ) 6 | 7 | var DatabaseNameValidators = []validator.String{ 8 | sqlIdentifierValidator{}, 9 | stringLengthValidator{1, 128}, 10 | } 11 | 12 | var LoginNameValidators = []validator.String{ 13 | sqlIdentifierValidator{}, 14 | } 15 | 16 | var UserNameValidators = []validator.String{ 17 | stringLengthValidator{Min: 1, Max: 128}, 18 | } 19 | 20 | var SchemaNameValidators = []validator.String{ 21 | stringLengthValidator{Min: 1, Max: 128}, 22 | } 23 | -------------------------------------------------------------------------------- /internal/validators/sqlIdentifier.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 7 | "github.com/hashicorp/terraform-plugin-framework/schema/validator" 8 | "regexp" 9 | ) 10 | 11 | // To ensure validator fully satisfies framework interfaces 12 | var _ validator.String = sqlIdentifierValidator{} 13 | 14 | type sqlIdentifierValidator struct{} 15 | 16 | func (s sqlIdentifierValidator) Description(context.Context) string { 17 | return "SQL identifier allows letters, digits, @, $, # or _, start with letter, _, @ or #" 18 | } 19 | 20 | func (s sqlIdentifierValidator) MarkdownDescription(context.Context) string { 21 | return "SQL identifier allows letters, digits, `@`, `$`, `#`, `-` or `_`, start with letter, `_`, `@` or `#`. See [MS SQL docs](https://docs.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers) for details." 22 | } 23 | 24 | func (s sqlIdentifierValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { 25 | if !common.IsAttrSet(request.ConfigValue) { 26 | return 27 | } 28 | 29 | if match, _ := regexp.Match("^[a-zA-Z_@#][a-zA-Z\\d@$#_-]*$", []byte(request.ConfigValue.ValueString())); !match { 30 | response.Diagnostics.AddAttributeError( 31 | request.Path, 32 | "Invalid SQL identifier", 33 | fmt.Sprintf("%s, got: %s", s.Description(ctx), request.ConfigValue.ValueString()), 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /internal/validators/sqlIdentifier_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/types" 5 | "testing" 6 | ) 7 | 8 | func TestSqlIdentifierValidate(t *testing.T) { 9 | const validationErrSummary = "Invalid SQL identifier" 10 | 11 | testCases := map[string]validatorTestCase{ 12 | "Unknown": { 13 | val: types.StringUnknown(), 14 | }, 15 | "Null": { 16 | val: types.StringNull(), 17 | }, 18 | "Valid": { 19 | val: types.StringValue("_idenTif@$#_er"), 20 | }, 21 | "startingWithDigit": { 22 | val: types.StringValue("2ndIdentifier"), 23 | expectedSummary: validationErrSummary, 24 | }, 25 | "withSpace": { 26 | val: types.StringValue("has space"), 27 | expectedSummary: validationErrSummary, 28 | }, 29 | "forbiddenChar": { 30 | val: types.StringValue("has&inName"), 31 | expectedSummary: validationErrSummary, 32 | }, 33 | } 34 | 35 | validatorTests(testCases, sqlIdentifierValidator{}, t) 36 | } 37 | -------------------------------------------------------------------------------- /internal/validators/stringLength.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/PGSSoft/terraform-provider-mssql/internal/services/common" 7 | "github.com/hashicorp/terraform-plugin-framework/schema/validator" 8 | ) 9 | 10 | var ( 11 | // To ensure validator fully satisfies framework interfaces 12 | _ validator.String = stringLengthValidator{} 13 | ) 14 | 15 | type stringLengthValidator struct { 16 | Min int 17 | Max int 18 | } 19 | 20 | func (s stringLengthValidator) Description(context.Context) string { 21 | return fmt.Sprintf("string length must be between %d and %d", s.Min, s.Max) 22 | } 23 | 24 | func (s stringLengthValidator) MarkdownDescription(context.Context) string { 25 | return fmt.Sprintf("string length must be between `%d` and `%d`", s.Min, s.Max) 26 | } 27 | 28 | func (s stringLengthValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { 29 | if !common.IsAttrSet(request.ConfigValue) { 30 | return 31 | } 32 | 33 | strLen := len(request.ConfigValue.ValueString()) 34 | 35 | if strLen < s.Min || strLen > s.Max { 36 | response.Diagnostics.AddAttributeError( 37 | request.Path, 38 | "Invalid String Length", 39 | fmt.Sprintf("%s, got: %s (%d).", s.Description(ctx), request.ConfigValue.ValueString(), strLen)) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /internal/validators/stringLength_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-framework/types" 5 | "testing" 6 | ) 7 | 8 | func TestStringLengthValidate(t *testing.T) { 9 | const validationErrSummary = "Invalid String Length" 10 | 11 | testCases := map[string]validatorTestCase{ 12 | "Unknown": { 13 | val: types.StringUnknown(), 14 | }, 15 | "Null": { 16 | val: types.StringNull(), 17 | }, 18 | "Valid": { 19 | val: types.StringValue("xxxxx"), 20 | }, 21 | "TooShort": { 22 | val: types.StringValue("xx"), 23 | expectedSummary: validationErrSummary, 24 | }, 25 | "TooLong": { 26 | val: types.StringValue("xxxxx xxxxx"), 27 | expectedSummary: validationErrSummary, 28 | }, 29 | } 30 | 31 | validatorTests(testCases, stringLengthValidator{Min: 5, Max: 10}, t) 32 | } 33 | -------------------------------------------------------------------------------- /internal/validators/utils_test.go: -------------------------------------------------------------------------------- 1 | package validators 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-framework/diag" 6 | "github.com/hashicorp/terraform-plugin-framework/schema/validator" 7 | "github.com/hashicorp/terraform-plugin-framework/types" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | type validatorTestCase struct { 13 | val types.String 14 | expectedSummary string 15 | } 16 | 17 | func validatorTests(testCases map[string]validatorTestCase, validatorImpl validator.String, t *testing.T) { 18 | for name, tc := range testCases { 19 | name, tc := name, tc 20 | t.Run(name, func(t *testing.T) { 21 | request := validator.StringRequest{ConfigValue: tc.val} 22 | response := validator.StringResponse{} 23 | 24 | validatorImpl.ValidateString(context.Background(), request, &response) 25 | 26 | if tc.expectedSummary == "" { 27 | assert.Falsef(t, response.Diagnostics.HasError(), "Unexpected validation errrors: %v", response.Diagnostics) 28 | } else { 29 | for _, d := range response.Diagnostics { 30 | if d.Severity() == diag.SeverityError && d.Summary() == tc.expectedSummary { 31 | return 32 | } 33 | } 34 | t.Errorf("Did not find expected validation error '%s'", tc.expectedSummary) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | 8 | "github.com/PGSSoft/terraform-provider-mssql/internal/provider" 9 | "github.com/hashicorp/terraform-plugin-framework/providerserver" 10 | ) 11 | 12 | //go:generate terraform fmt -recursive ./examples/ 13 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs 14 | 15 | var version = provider.VersionDev 16 | 17 | func main() { 18 | debug := flag.Bool("debug", false, "set to true to run the provider with support for debuggers like delve") 19 | flag.Parse() 20 | 21 | opts := providerserver.ServeOpts{ 22 | Address: "registry.terraform.io/PGSSoft/mssql", 23 | Debug: *debug, 24 | } 25 | 26 | if err := providerserver.Serve(context.Background(), provider.New(version), opts); err != nil { 27 | log.Fatal(err.Error()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /templates/index.md.tmpl: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "" 3 | page_title: "Provider: MS SQL Server" 4 | description: The provider can be used to configure objects in Microsoft SQL Server and Azure SQL instances. 5 | --- 6 | 7 | # MS SQL provider 8 | 9 | The provider can be used to configure objects in Microsoft SQL Server and Azure SQL instances. 10 | 11 | ## Authentication methods 12 | 13 | ### SQL auth 14 | Provider username and password, using `sql_auth` field: 15 | {{tffile "examples/provider/sql.tf"}} 16 | 17 | ### Azure AD 18 | In case of Azure SQL connections, Azure Active Directory auth tokens can be used to authenticate. 19 | 20 |
21 | #### Service Principal 22 | Provide client ID (a.k.a. application ID), secret and tenant ID, using `azure_auth` field: 23 | {{tffile "examples/provider/aad_sp.tf"}} 24 | 25 |
26 | #### Default chained credentials 27 | When `azure_auth` value is set to empty object (`{}`), the provider uses chained credentials built from `EnvironmentCredential` -> `ManagedIdentityCredential` -> `AzureCLICredential`. 28 | See [DefaultAzureCredential docs](https://github.com/Azure/azure-sdk-for-go/wiki/Set-up-Your-Environment-for-Authentication#configure-defaultazurecredential) for details. 29 | 30 |
31 | #### Environment variables 32 | When `azure_auth` value is set to empty object (`{}`) and following environment variables are set, the env variable values will be used for authentication, taking precedence over `DefaultAzureCredential`. 33 | - `ARM_CLIENT_ID` 34 | - `ARM_CLIENT_SECRET` 35 | - `ARM_TENANT_ID` 36 | 37 |
38 | Example: 39 | {{tffile "examples/provider/aad_default.tf"}} 40 | 41 | ## Computed connection provider configuration 42 | Provider can be used, with certain limitations, with computed provider configuration. For example, provider's `hostname` can be sourced from `azurerm_mssql_server.fully_qualified_domain_name`. As shown in this [Azure SQL example](https://github.com/PGSSoft/terraform-provider-mssql/tree/main/examples/provider/azure_sql.tf) 43 | 44 | ~> **Warning** When connection details are computed and not known during plan execution (e.g. SQL Server resource returning FQDN is planned to be recreated), the state cannot contain any previously created `mssql_*` resources. In such case error will be reported, as the provider does not have enough information to generate correct plan. 45 | 46 | {{ .SchemaMarkdown | trimspace }} -------------------------------------------------------------------------------- /terraform-registry-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "metadata": { 4 | "protocol_versions": ["6.0"] 5 | } 6 | } -------------------------------------------------------------------------------- /tests/azure_test_harness/data.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | public_ip = data.http.publicip_address.response_body 3 | } 4 | 5 | data "azurerm_client_config" "current" {} 6 | 7 | data "azurerm_resource_group" "tests" { 8 | name = "terraform-mssql-tests" 9 | } 10 | 11 | data "http" "publicip_address" { 12 | url = "https://api.ipify.org/" 13 | } -------------------------------------------------------------------------------- /tests/azure_test_harness/env.tpl: -------------------------------------------------------------------------------- 1 | %{ for name, value in envs ~} 2 | export ${name}=${value} 3 | %{ endfor ~} -------------------------------------------------------------------------------- /tests/azure_test_harness/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | name = "tfmsqltest${random_string.suffix.result}" 3 | } 4 | 5 | resource "random_string" "suffix" { 6 | length = 5 7 | special = false 8 | upper = false 9 | } 10 | 11 | data "environment_variables" "arm" { 12 | filter = "^ARM_" 13 | } -------------------------------------------------------------------------------- /tests/azure_test_harness/msi.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_user_assigned_identity" "this" { 2 | name = local.name 3 | resource_group_name = data.azurerm_resource_group.tests.name 4 | location = data.azurerm_resource_group.tests.location 5 | } 6 | 7 | data "azurerm_user_assigned_identity" "server" { 8 | name = "terraform-mssql-tests" 9 | resource_group_name = data.azurerm_resource_group.tests.name 10 | } -------------------------------------------------------------------------------- /tests/azure_test_harness/outputs.tf: -------------------------------------------------------------------------------- 1 | resource "local_file" "env" { 2 | filename = "${path.module}/../../local.env" 3 | content = templatefile("${path.module}/env.tpl", { 4 | envs = { 5 | TF_ACC = 1 6 | TF_MSSQL_HOST = azurerm_mssql_server.this.fully_qualified_domain_name 7 | TF_MSSQL_ELASTIC_POOL_NAME = azurerm_mssql_elasticpool.this.name 8 | TF_MSSQL_EDITION = "azure" 9 | TF_MSSQL_MSI_NAME = azurerm_user_assigned_identity.this.name 10 | TF_MSSQL_MSI_CLIENT_ID = azurerm_user_assigned_identity.this.client_id 11 | TF_MSSQL_MSI_OBJECT_ID = azurerm_user_assigned_identity.this.principal_id 12 | } 13 | }) 14 | } -------------------------------------------------------------------------------- /tests/azure_test_harness/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | publicip = { 4 | source = "nxt-engineering/publicip" 5 | } 6 | 7 | environment = { 8 | source = "EppO/environment" 9 | } 10 | } 11 | } 12 | 13 | provider "azurerm" { 14 | features {} 15 | } 16 | 17 | -------------------------------------------------------------------------------- /tests/azure_test_harness/server.tf: -------------------------------------------------------------------------------- 1 | resource "azurerm_mssql_server" "this" { 2 | name = local.name 3 | resource_group_name = data.azurerm_resource_group.tests.name 4 | location = data.azurerm_resource_group.tests.location 5 | version = "12.0" 6 | primary_user_assigned_identity_id = data.azurerm_user_assigned_identity.server.id 7 | 8 | identity { 9 | type = "UserAssigned" 10 | identity_ids = [data.azurerm_user_assigned_identity.server.id] 11 | } 12 | 13 | azuread_administrator { 14 | azuread_authentication_only = true 15 | login_username = lookup(data.environment_variables.arm.items, "ARM_CLIENT_ID", data.azurerm_client_config.current.object_id) 16 | object_id = lookup(data.environment_variables.arm.items, "ARM_CLIENT_ID", data.azurerm_client_config.current.object_id) 17 | } 18 | } 19 | 20 | resource "azurerm_mssql_firewall_rule" "self" { 21 | name = "test-runner" 22 | server_id = azurerm_mssql_server.this.id 23 | start_ip_address = local.public_ip 24 | end_ip_address = local.public_ip 25 | } 26 | 27 | resource "azurerm_mssql_elasticpool" "this" { 28 | name = azurerm_mssql_server.this.name 29 | resource_group_name = azurerm_mssql_server.this.resource_group_name 30 | location = azurerm_mssql_server.this.location 31 | server_name = azurerm_mssql_server.this.name 32 | max_size_gb = 5 33 | 34 | per_database_settings { 35 | max_capacity = 4 36 | min_capacity = 0.25 37 | } 38 | 39 | sku { 40 | capacity = 4 41 | name = "GP_Gen5" 42 | tier = "GeneralPurpose" 43 | family = "Gen5" 44 | } 45 | } -------------------------------------------------------------------------------- /tests/docker_test_harness/env.tpl: -------------------------------------------------------------------------------- 1 | %{ for name, value in envs ~} 2 | export ${name}=${value} 3 | %{ endfor ~} -------------------------------------------------------------------------------- /tests/docker_test_harness/main.tf: -------------------------------------------------------------------------------- 1 | resource "random_password" "mssql" { 2 | length = 20 3 | special = false 4 | } 5 | 6 | resource "docker_image" "mssql" { 7 | name = "mcr.microsoft.com/mssql/server:${var.mssql_version}-latest" 8 | keep_locally = true 9 | } 10 | 11 | resource "docker_container" "mssql" { 12 | image = docker_image.mssql.image_id 13 | name = "terraform-mssql-acc-test" 14 | 15 | env = [ 16 | "ACCEPT_EULA=Y", 17 | "MSSQL_SA_PASSWORD=${random_password.mssql.result}" 18 | ] 19 | 20 | ports { 21 | internal = 1433 22 | external = 11433 23 | } 24 | } 25 | 26 | resource "time_sleep" "mssql_start" { 27 | create_duration = "5s" 28 | 29 | triggers = { 30 | container = docker_container.mssql.id 31 | } 32 | } -------------------------------------------------------------------------------- /tests/docker_test_harness/outputs.tf: -------------------------------------------------------------------------------- 1 | resource "local_file" "env" { 2 | filename = "${path.module}/../../local.env" 3 | content = templatefile("${path.module}/env.tpl", { 4 | envs = { 5 | TF_ACC = 1 6 | TF_MSSQL_HOST = "localhost:${docker_container.mssql.ports[0].external}" 7 | TF_MSSQL_PASSWORD = random_password.mssql.result 8 | TF_MSSQL_EDITION = "docker" 9 | } 10 | }) 11 | } -------------------------------------------------------------------------------- /tests/docker_test_harness/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | docker = { 4 | source = "kreuzwerker/docker" 5 | } 6 | } 7 | } 8 | 9 | provider "docker" {} -------------------------------------------------------------------------------- /tests/docker_test_harness/variables.tf: -------------------------------------------------------------------------------- 1 | variable "mssql_version" { 2 | type = string 3 | default = "2019" 4 | } -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | 3 | package tools 4 | 5 | import _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" 6 | --------------------------------------------------------------------------------