From 367a1b731103959fadd392a8d31adee437e8fafb Mon Sep 17 00:00:00 2001
From: P-Cao <105041254+P-Cao@users.noreply.github.com>
Date: Thu, 3 Aug 2023 20:27:29 +0800
Subject: [PATCH] Implement Resource and Datasource for SMB Share (#7)
* Implement Resource of SMB Share
* Set name attribute as required, update resource helper & datasource AT
Update resource helper to copy attribute type of nil pointer
Update case of NullableString
Set name attribute as required
Add resource config in "get all" case
---
client/client.go | 2 +-
docs/data-sources/smb_share.md | 133 ++++
docs/resources/smb_share.md | 176 +++++
examples/README.md | 16 +
.../powerscale_smb_share/datasource.tf | 35 +
.../powerscale_smb_share/provider.tf | 36 +
.../resources/powerscale_smb_share/import.sh | 20 +
.../powerscale_smb_share/provider.tf | 36 +
.../powerscale_smb_share/resource.tf | 32 +
go.mod | 4 +
go.sum | 5 +
powerscale/helper/cluster_helper.go | 17 +
powerscale/helper/resource_helper.go | 553 +++++++++++++++
powerscale/helper/resource_helper_test.go | 137 ++++
powerscale/helper/smb_share_helper.go | 49 ++
powerscale/models/cluster.go | 17 +
powerscale/models/smb_share.go | 228 +++++++
powerscale/provider/cluster_data_source.go | 17 +
powerscale/provider/provider.go | 2 +
powerscale/provider/provider_test.go | 8 +-
powerscale/provider/smb_share_data_source.go | 503 ++++++++++++++
.../provider/smb_share_data_source_test.go | 170 +++++
powerscale/provider/smb_share_resource.go | 636 ++++++++++++++++++
.../provider/smb_share_resource_test.go | 288 ++++++++
24 files changed, 3114 insertions(+), 6 deletions(-)
create mode 100644 docs/data-sources/smb_share.md
create mode 100644 docs/resources/smb_share.md
create mode 100644 examples/data-sources/powerscale_smb_share/datasource.tf
create mode 100644 examples/data-sources/powerscale_smb_share/provider.tf
create mode 100644 examples/resources/powerscale_smb_share/import.sh
create mode 100644 examples/resources/powerscale_smb_share/provider.tf
create mode 100644 examples/resources/powerscale_smb_share/resource.tf
create mode 100644 powerscale/helper/resource_helper.go
create mode 100644 powerscale/helper/resource_helper_test.go
create mode 100644 powerscale/helper/smb_share_helper.go
create mode 100644 powerscale/models/smb_share.go
create mode 100644 powerscale/provider/smb_share_data_source.go
create mode 100644 powerscale/provider/smb_share_data_source_test.go
create mode 100644 powerscale/provider/smb_share_resource.go
create mode 100644 powerscale/provider/smb_share_resource_test.go
diff --git a/client/client.go b/client/client.go
index 4e4ec626..ca467e3a 100644
--- a/client/client.go
+++ b/client/client.go
@@ -137,7 +137,7 @@ func NewOpenAPIClient(ctx context.Context, endpoint string, insecure bool, verbo
OperationServers: map[string]powerscale.ServerConfigurations{},
}
cfg.DefaultHeader = getHeaders()
- fmt.Printf("config %+v header %+v", cfg, cfg.DefaultHeader)
+ fmt.Printf("config %+v header %+v\n", cfg, cfg.DefaultHeader)
cfg.AddDefaultHeader("Authorization", "Basic "+basicAuthString)
apiClient := powerscale.NewAPIClient(&cfg)
return apiClient, nil
diff --git a/docs/data-sources/smb_share.md b/docs/data-sources/smb_share.md
new file mode 100644
index 00000000..2efaa424
--- /dev/null
+++ b/docs/data-sources/smb_share.md
@@ -0,0 +1,133 @@
+---
+# Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+#
+# Licensed under the Mozilla Public License Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://mozilla.org/MPL/2.0/
+#
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+title: "powerscale_smb_share data source"
+linkTitle: "powerscale_smb_share"
+page_title: "powerscale_smb_share Data Source - terraform-provider-powerscale"
+subcategory: ""
+description: |-
+ Data source for reading SMB Shares in PowerScale array.
+---
+
+# powerscale_smb_share (Data Source)
+
+Data source for reading SMB Shares in PowerScale array.
+
+
+
+
+## Schema
+
+### Optional
+
+- `filter` (Block, Optional) (see [below for nested schema](#nestedblock--filter))
+
+### Read-Only
+
+- `id` (String) Placeholder for acc testing
+- `smb_shares` (Attributes List) List of smb shares (see [below for nested schema](#nestedatt--smb_shares))
+
+
+### Nested Schema for `filter`
+
+Optional:
+
+- `dir` (String) The direction of the sort.
+- `limit` (Number) Return no more than this many results at once (see resume).
+- `names` (Set of String) Names to filter smb shares.
+- `offset` (Number) The position of the first item returned for a paginated query within the full result set.
+- `resolve_names` (Boolean) If true, resolve group and user names in personas.
+- `resume` (String) Continue returning results from previous call using this token (token should come from the previous call, resume cannot be used with other options).
+- `scope` (String) If specified as "effective" or not specified, all fields are returned. If specified as "user", only fields with non-default values are shown. If specified as "default", the original values are returned.
+- `sort` (String) The field that will be used for sorting.
+- `zone` (String) Specifies which access zone to use.
+
+
+
+### Nested Schema for `smb_shares`
+
+Read-Only:
+
+- `access_based_enumeration` (Boolean) Only enumerate files and folders the requesting user has access to.
+- `access_based_enumeration_root_only` (Boolean) Access-based enumeration on only the root directory of the share.
+- `allow_delete_readonly` (Boolean) Allow deletion of read-only files in the share.
+- `allow_execute_always` (Boolean) Allows users to execute files they have read rights for.
+- `allow_variable_expansion` (Boolean) Allow automatic expansion of variables for home directories.
+- `auto_create_directory` (Boolean) Automatically create home directories.
+- `browsable` (Boolean) Share is visible in net view and the browse list.
+- `ca_timeout` (Number) Persistent open timeout for the share.
+- `ca_write_integrity` (String) Specify the level of write-integrity on continuously available shares.
+- `change_notify` (String) Level of change notification alerts on the share.
+- `continuously_available` (Boolean) Specify if persistent opens are allowed on the share.
+- `create_permissions` (String) Create permissions for new files and directories in share.
+- `csc_policy` (String) Client-side caching policy for the shares.
+- `description` (String) Description for this SMB share.
+- `directory_create_mask` (Number) Directory create mask bits.
+- `directory_create_mode` (Number) Directory create mode bits.
+- `file_create_mask` (Number) File create mask bits.
+- `file_create_mode` (Number) File create mode bits.
+- `file_filter_extensions` (List of String) Specifies the list of file extensions.
+- `file_filter_type` (String) Specifies if filter list is for deny or allow. Default is deny.
+- `file_filtering_enabled` (Boolean) Enables file filtering on this zone.
+- `hide_dot_files` (Boolean) Hide files and directories that begin with a period '.'.
+- `host_acl` (List of String) An ACL expressing which hosts are allowed access. A deny clause must be the final entry.
+- `id` (String) Share ID.
+- `impersonate_guest` (String) Specify the condition in which user access is done as the guest account.
+- `impersonate_user` (String) User account to be used as guest account.
+- `inheritable_path_acl` (Boolean) Set the inheritable ACL on the share path.
+- `mangle_byte_start` (Number) Specifies the wchar_t starting point for automatic byte mangling.
+- `mangle_map` (List of String) Character mangle map.
+- `name` (String) Share name.
+- `ntfs_acl_support` (Boolean) Support NTFS ACLs on files and directories.
+- `oplocks` (Boolean) Support oplocks.
+- `path` (String) Path of share within /ifs.
+- `permissions` (Attributes List) Specifies an ordered list of permission modifications. (see [below for nested schema](#nestedatt--smb_shares--permissions))
+- `run_as_root` (Attributes List) Allow account to run as root. (see [below for nested schema](#nestedatt--smb_shares--run_as_root))
+- `smb3_encryption_enabled` (Boolean) Enables SMB3 encryption for the share.
+- `sparse_file` (Boolean) Enables sparse file.
+- `strict_ca_lockout` (Boolean) Specifies if persistent opens would do strict lockout on the share.
+- `strict_flush` (Boolean) Handle SMB flush operations.
+- `strict_locking` (Boolean) Specifies whether byte range locks contend against SMB I/O.
+- `zid` (Number) Numeric ID of the access zone which contains this SMB share
+
+
+### Nested Schema for `smb_shares.permissions`
+
+Read-Only:
+
+- `permission` (String) Specifies the file system rights that are allowed or denied.
+- `permission_type` (String) Determines whether the permission is allowed or denied.
+- `trustee` (Attributes) Specifies the persona of the file group. (see [below for nested schema](#nestedatt--smb_shares--permissions--trustee))
+
+
+### Nested Schema for `smb_shares.permissions.trustee`
+
+Read-Only:
+
+- `id` (String) Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.
+- `name` (String) Specifies the persona name, which must be combined with a type.
+- `type` (String) Specifies the type of persona, which must be combined with a name.
+
+
+
+
+### Nested Schema for `smb_shares.run_as_root`
+
+Read-Only:
+
+- `id` (String) Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.
+- `name` (String) Specifies the persona name, which must be combined with a type.
+- `type` (String) Specifies the type of persona, which must be combined with a name.
\ No newline at end of file
diff --git a/docs/resources/smb_share.md b/docs/resources/smb_share.md
new file mode 100644
index 00000000..28a5d6ed
--- /dev/null
+++ b/docs/resources/smb_share.md
@@ -0,0 +1,176 @@
+---
+# Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+#
+# Licensed under the Mozilla Public License Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://mozilla.org/MPL/2.0/
+#
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+title: "powerscale_smb_share resource"
+linkTitle: "powerscale_smb_share"
+page_title: "powerscale_smb_share Resource - terraform-provider-powerscale"
+subcategory: ""
+description: |-
+ Resource for managing SMB Shares in PowerScale array.
+---
+
+# powerscale_smb_share (Resource)
+
+Resource for managing SMB Shares in PowerScale array.
+
+
+## Example Usage
+
+```terraform
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+resource "powerscale_smb_share" "share_example" {
+ auto_create_directory = true
+ name = "smb_share_example"
+ path = "/ifs/smb_share_example"
+ permissions = [
+ {
+ permission = "full"
+ permission_type = "allow"
+ trustee = {
+ id = "SID:S-1-1-0",
+ name = "Everyone",
+ type = "wellknown"
+ }
+ }
+ ]
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) Share name.
+- `path` (String) Path of share within /ifs.
+- `permissions` (Attributes List) Specifies an ordered list of permission modifications. (see [below for nested schema](#nestedatt--permissions))
+
+### Optional
+
+- `access_based_enumeration` (Boolean) Only enumerate files and folders the requesting user has access to.
+- `access_based_enumeration_root_only` (Boolean) Access-based enumeration on only the root directory of the share.
+- `allow_delete_readonly` (Boolean) Allow deletion of read-only files in the share.
+- `allow_execute_always` (Boolean) Allows users to execute files they have read rights for.
+- `allow_variable_expansion` (Boolean) Allow automatic expansion of variables for home directories.
+- `auto_create_directory` (Boolean) Automatically create home directories.
+- `browsable` (Boolean) Share is visible in net view and the browse list.
+- `ca_timeout` (Number) Persistent open timeout for the share.
+- `ca_write_integrity` (String) Specify the level of write-integrity on continuously available shares.
+- `change_notify` (String) Level of change notification alerts on the share.
+- `create_path` (Boolean) Create path if does not exist.
+- `create_permissions` (String) Create permissions for new files and directories in share.
+- `csc_policy` (String) Client-side caching policy for the shares.
+- `description` (String) Description for this SMB share.
+- `directory_create_mask` (Number) Directory create mask bits.
+- `directory_create_mode` (Number) Directory create mode bits.
+- `file_create_mask` (Number) File create mask bits.
+- `file_create_mode` (Number) File create mode bits.
+- `file_filter_extensions` (List of String) Specifies the list of file extensions.
+- `file_filter_type` (String) Specifies if filter list is for deny or allow. Default is deny.
+- `file_filtering_enabled` (Boolean) Enables file filtering on this zone.
+- `hide_dot_files` (Boolean) Hide files and directories that begin with a period '.'.
+- `host_acl` (List of String) An ACL expressing which hosts are allowed access. A deny clause must be the final entry.
+- `impersonate_guest` (String) Specify the condition in which user access is done as the guest account.
+- `impersonate_user` (String) User account to be used as guest account.
+- `inheritable_path_acl` (Boolean) Set the inheritable ACL on the share path.
+- `mangle_byte_start` (Number) Specifies the wchar_t starting point for automatic byte mangling.
+- `mangle_map` (List of String) Character mangle map.
+- `ntfs_acl_support` (Boolean) Support NTFS ACLs on files and directories.
+- `oplocks` (Boolean) Support oplocks.
+- `run_as_root` (Attributes List) Allow account to run as root. (see [below for nested schema](#nestedatt--run_as_root))
+- `smb3_encryption_enabled` (Boolean) Enables SMB3 encryption for the share.
+- `sparse_file` (Boolean) Enables sparse file.
+- `strict_ca_lockout` (Boolean) Specifies if persistent opens would do strict lockout on the share.
+- `strict_flush` (Boolean) Handle SMB flush operations.
+- `strict_locking` (Boolean) Specifies whether byte range locks contend against SMB I/O.
+- `zid` (Number) Numeric ID of the access zone which contains this SMB share.
+- `zone` (String) Name of the access zone to which to move this SMB share.
+
+### Read-Only
+
+- `continuously_available` (Boolean) Specify if persistent opens are allowed on the share.
+- `id` (String) The ID of the smb share.
+
+
+### Nested Schema for `permissions`
+
+Required:
+
+- `permission` (String) Specifies the file system rights that are allowed or denied.
+- `permission_type` (String) Determines whether the permission is allowed or denied.
+- `trustee` (Attributes) Specifies the persona of the file group. (see [below for nested schema](#nestedatt--permissions--trustee))
+
+
+### Nested Schema for `permissions.trustee`
+
+Optional:
+
+- `id` (String) Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.
+- `name` (String) Specifies the persona name, which must be combined with a type.
+- `type` (String) Specifies the type of persona, which must be combined with a name.
+
+
+
+
+### Nested Schema for `run_as_root`
+
+Optional:
+
+- `id` (String) Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.
+- `name` (String) Specifies the persona name, which must be combined with a type.
+- `type` (String) Specifies the type of persona, which must be combined with a name.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+# Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+# Licensed under the Mozilla Public License Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://mozilla.org/MPL/2.0/
+
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# The command is
+# terraform import powermax_host.example_share
+# Example:
+terraform import powerscale_smb_share.example_share example_share
+# after running this command, populate the name field in the config file to start managing this resource
+```
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
index 026c42c7..ac396860 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,3 +1,19 @@
+---
+# Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+#
+# Licensed under the Mozilla Public License Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://mozilla.org/MPL/2.0/
+#
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
# Examples
This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI.
diff --git a/examples/data-sources/powerscale_smb_share/datasource.tf b/examples/data-sources/powerscale_smb_share/datasource.tf
new file mode 100644
index 00000000..f1e0e053
--- /dev/null
+++ b/examples/data-sources/powerscale_smb_share/datasource.tf
@@ -0,0 +1,35 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+data "powerscale_smb_share" "test" {
+ filter {
+ zone = "System"
+ limit = 1
+ names = ["tfacc_smb_share"]
+ }
+}
+
+output "powerscale_smb_share" {
+ value = data.powerscale_smb_share.test
+}
+
+data "powerscale_smb_share" "all" {
+}
+
+output "powerscale_smb_share_data_all" {
+ value = data.powerscale_smb_share.all
+}
\ No newline at end of file
diff --git a/examples/data-sources/powerscale_smb_share/provider.tf b/examples/data-sources/powerscale_smb_share/provider.tf
new file mode 100644
index 00000000..75c69ec0
--- /dev/null
+++ b/examples/data-sources/powerscale_smb_share/provider.tf
@@ -0,0 +1,36 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+terraform {
+ required_providers {
+ powerscale = {
+ source = "registry.terraform.io/dell/powerscale"
+ }
+ }
+}
+
+provider "powerscale" {
+ username = var.username
+ password = var.password
+ endpoint = var.endpoint
+ insecure = var.insecure
+ group = var.group
+ volume_path = var.volume_path
+ volume_path_permissions = var.volume_path_permissions
+ ignore_unresolvable_hosts = var.ignore_unresolvable_hosts
+ auth_type = var.auth_type
+ verbose_logging = var.verbose_logging
+}
\ No newline at end of file
diff --git a/examples/resources/powerscale_smb_share/import.sh b/examples/resources/powerscale_smb_share/import.sh
new file mode 100644
index 00000000..c94f7dc7
--- /dev/null
+++ b/examples/resources/powerscale_smb_share/import.sh
@@ -0,0 +1,20 @@
+# Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+# Licensed under the Mozilla Public License Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://mozilla.org/MPL/2.0/
+
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# The command is
+# terraform import powermax_host.example_share
+# Example:
+terraform import powerscale_smb_share.example_share example_share
+# after running this command, populate the name field in the config file to start managing this resource
diff --git a/examples/resources/powerscale_smb_share/provider.tf b/examples/resources/powerscale_smb_share/provider.tf
new file mode 100644
index 00000000..75c69ec0
--- /dev/null
+++ b/examples/resources/powerscale_smb_share/provider.tf
@@ -0,0 +1,36 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+terraform {
+ required_providers {
+ powerscale = {
+ source = "registry.terraform.io/dell/powerscale"
+ }
+ }
+}
+
+provider "powerscale" {
+ username = var.username
+ password = var.password
+ endpoint = var.endpoint
+ insecure = var.insecure
+ group = var.group
+ volume_path = var.volume_path
+ volume_path_permissions = var.volume_path_permissions
+ ignore_unresolvable_hosts = var.ignore_unresolvable_hosts
+ auth_type = var.auth_type
+ verbose_logging = var.verbose_logging
+}
\ No newline at end of file
diff --git a/examples/resources/powerscale_smb_share/resource.tf b/examples/resources/powerscale_smb_share/resource.tf
new file mode 100644
index 00000000..264c6bca
--- /dev/null
+++ b/examples/resources/powerscale_smb_share/resource.tf
@@ -0,0 +1,32 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+resource "powerscale_smb_share" "share_example" {
+ auto_create_directory = true
+ name = "smb_share_example"
+ path = "/ifs/smb_share_example"
+ permissions = [
+ {
+ permission = "full"
+ permission_type = "allow"
+ trustee = {
+ id = "SID:S-1-1-0",
+ name = "Everyone",
+ type = "wellknown"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index f500abe4..2341c04c 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
github.com/hashicorp/terraform-plugin-testing v1.3.0
github.com/joho/godotenv v1.5.1
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f
+ github.com/stretchr/testify v1.8.2
)
require (
@@ -36,6 +37,7 @@ require (
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/bytedance/mockey v1.2.4
github.com/cloudflare/circl v1.3.3 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
@@ -71,6 +73,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/oklog/run v1.0.0 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/russross/blackfriday v1.6.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
@@ -92,6 +95,7 @@ require (
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/grpc v1.56.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
replace dell/powerscale-go-client => ./powerscale-go-client
diff --git a/go.sum b/go.sum
index 1f582c44..707c455e 100644
--- a/go.sum
+++ b/go.sum
@@ -177,13 +177,18 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
diff --git a/powerscale/helper/cluster_helper.go b/powerscale/helper/cluster_helper.go
index f13f361d..45ea3c47 100644
--- a/powerscale/helper/cluster_helper.go
+++ b/powerscale/helper/cluster_helper.go
@@ -1,3 +1,20 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package helper
import (
diff --git a/powerscale/helper/resource_helper.go b/powerscale/helper/resource_helper.go
new file mode 100644
index 00000000..5a35e86a
--- /dev/null
+++ b/powerscale/helper/resource_helper.go
@@ -0,0 +1,553 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package helper
+
+import (
+ "context"
+ powerscale "dell/powerscale-go-client"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/tfsdk"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "math/big"
+ "reflect"
+ "strings"
+)
+
+// CopyFieldsToNonNestedModel copy OpenAPI struct source to destination of struct with terraform types.
+// use this function when model struct contains only types.List/Object
+func CopyFieldsToNonNestedModel(ctx context.Context, source, destination interface{}) error {
+ tflog.Debug(ctx, "Copy fields", map[string]interface{}{
+ "source": source,
+ "destination": destination,
+ })
+ var err error
+ sourceValue := reflect.ValueOf(source)
+ destinationValue := reflect.ValueOf(destination)
+
+ // Check if destination is a pointer to a struct
+ if destinationValue.Kind() != reflect.Ptr || destinationValue.Elem().Kind() != reflect.Struct {
+ return fmt.Errorf("destination is not a pointer to a struct")
+ }
+
+ // if source is a pointer, use the Elem() method to get the value that the pointer points to
+ if sourceValue.Kind() == reflect.Ptr {
+ sourceValue = sourceValue.Elem()
+ }
+
+ if sourceValue.Kind() != reflect.Struct {
+ return fmt.Errorf("source is not a struct")
+ }
+
+ // Get the type of the destination struct
+ //destinationType := destinationValue.Elem().Type()
+ for i := 0; i < sourceValue.NumField(); i++ {
+ sourceFieldTag := getFieldJSONTag(sourceValue, i)
+
+ tflog.Debug(ctx, "Converting source field", map[string]interface{}{
+ "sourceFieldTag": sourceFieldTag,
+ "sourceFieldKind": sourceValue.Field(i).Kind().String(),
+ })
+
+ sourceField := sourceValue.Field(i)
+ if sourceField.Kind() == reflect.Ptr {
+ sourceField = sourceField.Elem()
+ }
+ destinationField := getFieldByTfTag(destinationValue.Elem(), sourceFieldTag)
+ structType := reflect.TypeOf(source)
+ // For zero value (nil), the object still need to pass type information into it
+ if !sourceField.IsValid() {
+ destinationField = getFieldByTfTag(destinationValue.Elem(), sourceFieldTag)
+ mapType, err := getStructAttrTypeFromType(ctx, structType.Field(i).Type)
+ if err != nil {
+ return err
+ }
+ destinationField.Set(reflect.ValueOf(types.ObjectNull(mapType)))
+ continue
+ }
+ if destinationField.IsValid() && destinationField.CanSet() {
+ tflog.Debug(ctx, "debugging source field", map[string]interface{}{
+ "sourceField Interface": sourceField.Interface(),
+ })
+ // Convert the source value to the type of the destination field dynamically
+ var destinationFieldValue attr.Value
+
+ switch sourceField.Kind() {
+ case reflect.String:
+ destinationFieldValue = types.StringValue(sourceField.String())
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ destinationFieldValue = types.Int64Value(sourceField.Int())
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ destinationFieldValue = types.Int64Value(sourceField.Int())
+ case reflect.Float32, reflect.Float64:
+ //destinationFieldValue = types.Float64Value(sourceField.Float())
+ destinationFieldValue = types.NumberValue(big.NewFloat(sourceField.Float()))
+ case reflect.Bool:
+ destinationFieldValue = types.BoolValue(sourceField.Bool())
+ case reflect.Array, reflect.Slice:
+ destinationFieldValue, err = getSliceAttrValue(ctx, sourceField.Interface())
+ if err != nil {
+ return err
+ }
+ case reflect.Struct:
+ destinationFieldValue, err = getStructValue(ctx, sourceField.Interface())
+ if err != nil {
+ return err
+ }
+ default:
+ tflog.Error(ctx, "unsupported source field type", map[string]interface{}{
+ "sourceField": sourceField,
+ })
+ continue
+ }
+ if destinationField.Type() == reflect.TypeOf(destinationFieldValue) {
+ destinationField.Set(reflect.ValueOf(destinationFieldValue))
+ }
+ }
+ }
+
+ return nil
+}
+
+func getStructAttrTypeFromType(ctx context.Context, structType reflect.Type) (map[string]attr.Type, error) {
+ attrTypeMap := make(map[string]attr.Type)
+ if structType.Kind() == reflect.Ptr {
+ structType = structType.Elem()
+ }
+ for fieldIndex := 0; fieldIndex < structType.NumField(); fieldIndex++ {
+ structField := structType.Field(fieldIndex)
+ tag := structField.Tag.Get("json")
+ tag = strings.TrimSuffix(tag, ",omitempty")
+ structFieldKind := structField.Type.Kind()
+ if structField.Type.Kind() == reflect.Ptr {
+ structFieldKind = structField.Type.Elem().Kind()
+ }
+ switch structFieldKind {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ attrTypeMap[tag] = types.Int64Type
+ case reflect.String:
+ attrTypeMap[tag] = types.StringType
+ case reflect.Float32, reflect.Float64:
+ attrTypeMap[tag] = types.NumberType
+ case reflect.Bool:
+ attrTypeMap[tag] = types.BoolType
+ case reflect.Struct:
+ if structField.Type == reflect.TypeOf(powerscale.NullableString{}) {
+ attrTypeMap[tag] = types.StringType
+ } else {
+ structAttrType, err := getStructAttrTypeFromType(ctx, structField.Type)
+ if err != nil {
+ return nil, err
+ }
+ attrTypeMap[tag] = types.ObjectType{AttrTypes: structAttrType}
+ }
+ case reflect.Array, reflect.Slice:
+ structAttrType, err := getSliceAttrTypeFromType(ctx, structField.Type)
+ if err != nil {
+ return nil, err
+ }
+ attrTypeMap[tag] = structAttrType
+ }
+ }
+ return attrTypeMap, nil
+
+}
+
+func getSliceAttrTypeFromType(ctx context.Context, sliceType reflect.Type) (attr.Type, error) {
+ sliceType = sliceType.Elem()
+ if sliceType.Kind() == reflect.Ptr {
+ sliceType = sliceType.Elem()
+ }
+ switch sliceType.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return types.ListType{ElemType: types.Int64Type}, nil
+ case reflect.String:
+ return types.ListType{ElemType: types.StringType}, nil
+ case reflect.Float32, reflect.Float64:
+ return types.ListType{ElemType: types.NumberType}, nil
+ case reflect.Bool:
+ return types.ListType{ElemType: types.BoolType}, nil
+ case reflect.Struct:
+ structAttrType, err := getStructAttrTypeFromType(ctx, sliceType)
+ if err != nil {
+ return nil, err
+ }
+ return types.ListType{ElemType: types.ObjectType{AttrTypes: structAttrType}}, nil
+ case reflect.Array, reflect.Slice:
+ sliceAttrType, err := getSliceAttrTypeFromType(ctx, sliceType)
+ if err != nil {
+ return nil, err
+ }
+ return types.ListType{ElemType: sliceAttrType}, nil
+ default:
+ return nil, fmt.Errorf("unknown type")
+ }
+}
+
+func getStructValue(ctx context.Context, structObj interface{}) (basetypes.ObjectValue, error) {
+ elem := reflect.ValueOf(structObj)
+ attrType, err := getStructAttrTypeFromType(ctx, reflect.TypeOf(structObj))
+ if err != nil {
+ return types.ObjectNull(nil), err
+ }
+ valueMap := make(map[string]attr.Value)
+ // iterate the listObject
+ for fieldIndex := 0; fieldIndex < elem.NumField(); fieldIndex++ {
+ tag := elem.Type().Field(fieldIndex).Tag.Get("json")
+ tag = strings.TrimSuffix(tag, ",omitempty")
+ elemFieldVal := elem.Field(fieldIndex)
+ elemFieldType := elemFieldVal.Type()
+ if elemFieldType.Kind() == reflect.Ptr {
+ elemFieldVal = elemFieldVal.Elem()
+ elemFieldType = elemFieldType.Elem()
+ }
+ switch elemFieldType.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ valueMap[tag] = types.Int64Value(elemFieldVal.Int())
+ case reflect.String:
+ valueMap[tag] = types.StringValue(elemFieldVal.String())
+ case reflect.Bool:
+ valueMap[tag] = types.BoolValue(elemFieldVal.Bool())
+ case reflect.Struct:
+ if elemFieldType == reflect.TypeOf(powerscale.NullableString{}) {
+ nullableString, ok := elemFieldVal.Interface().(powerscale.NullableString)
+ if !ok {
+ return types.ObjectNull(nil), fmt.Errorf("NullableString failed")
+ }
+ valueMap[tag] = types.StringValue(*nullableString.Get())
+ } else {
+ valueMap[tag], err = getStructValue(ctx, elemFieldVal.Interface())
+ if err != nil {
+ return types.ObjectNull(nil), err
+ }
+ }
+ case reflect.Array, reflect.Slice:
+ valueMap[tag], err = getSliceAttrValue(ctx, elemFieldVal.Interface())
+ if err != nil {
+ return types.ObjectNull(nil), err
+ }
+ }
+ }
+ object, _ := types.ObjectValue(attrType, valueMap)
+ return object, nil
+}
+
+func getSliceAttrValue(ctx context.Context, sliceObject interface{}) (attr.Value, error) {
+ sliceValue := reflect.ValueOf(sliceObject)
+ switch sliceValue.Type().Elem().Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ listValue, _ := types.ListValueFrom(ctx, types.Int64Type, sliceObject)
+ return listValue, nil
+ case reflect.String:
+ listValue, _ := types.ListValueFrom(ctx, types.StringType, sliceObject)
+ return listValue, nil
+ case reflect.Float32, reflect.Float64:
+ listValue, _ := types.ListValueFrom(ctx, types.NumberType, sliceObject)
+ return listValue, nil
+ case reflect.Bool:
+ listValue, _ := types.ListValueFrom(ctx, types.BoolType, sliceObject)
+ return listValue, nil
+ case reflect.Struct:
+ var values []attr.Value
+ sliceElemType, err := getStructAttrTypeFromType(ctx, sliceValue.Type().Elem())
+ if err != nil {
+ return nil, err
+ }
+ for index := 0; index < sliceValue.Len(); index++ {
+ sliceElemValue, err := getStructValue(ctx, sliceValue.Index(index).Interface())
+ if err != nil {
+ return nil, err
+ }
+ values = append(values, sliceElemValue)
+ }
+ if len(values) == 0 {
+ return types.ListNull(types.ObjectType{AttrTypes: sliceElemType}), nil
+ }
+ returnListValue, _ := types.ListValue(types.ObjectType{AttrTypes: sliceElemType}, values)
+ return returnListValue, nil
+ case reflect.Array, reflect.Slice:
+ var values []attr.Value
+ sliceAttrType, err := getSliceAttrTypeFromType(ctx, sliceValue.Type().Elem())
+ if err != nil {
+ return nil, err
+ }
+ for index := 0; index < sliceValue.Len(); index++ {
+ sliceElemValue, err := getSliceAttrValue(ctx, sliceValue.Index(index).Interface())
+ if err != nil {
+ return nil, err
+ }
+ values = append(values, sliceElemValue)
+ }
+ if len(values) == 0 {
+ return types.ListNull(types.ListType{ElemType: sliceAttrType}), nil
+ }
+ returnListValue, _ := types.ListValue(types.ListType{ElemType: sliceAttrType}, values)
+ return returnListValue, nil
+ default:
+ return nil, fmt.Errorf("unknown type")
+ }
+}
+
+// ReadFromState read from model to openapi struct, model should not contain nested struct
+func ReadFromState(ctx context.Context, source, destination interface{}) error {
+ sourceValue := reflect.ValueOf(source)
+ destinationValue := reflect.ValueOf(destination)
+ if destinationValue.Kind() != reflect.Ptr || destinationValue.Elem().Kind() != reflect.Struct {
+ return fmt.Errorf("destination is not a pointer to a struct")
+ }
+ if sourceValue.Kind() == reflect.Ptr {
+ sourceValue = sourceValue.Elem()
+ }
+ if sourceValue.Kind() != reflect.Struct {
+ return fmt.Errorf("source is not a struct")
+ }
+ for i := 0; i < sourceValue.NumField(); i++ {
+ sourceFieldTag := sourceValue.Type().Field(i).Tag.Get("tfsdk")
+ destinationField, err := getFieldByJSONTag(destinationValue.Elem().Addr().Interface(), sourceFieldTag)
+ if err != nil {
+ // Not found, skip the field
+ continue
+ }
+ if destinationField.IsValid() && destinationField.CanSet() {
+ switch sourceValue.Field(i).Interface().(type) {
+ case basetypes.StringValue:
+ stringVal, ok := sourceValue.Field(i).Interface().(basetypes.StringValue)
+ if !ok || stringVal.IsNull() || stringVal.IsUnknown() {
+ continue
+ }
+ targetValue := stringVal.ValueString()
+ if destinationField.Kind() == reflect.Ptr && destinationField.Type().Elem().Kind() == reflect.String {
+ destinationField.Set(reflect.ValueOf(&targetValue))
+ }
+ if destinationField.Type().Kind() == reflect.String {
+ destinationField.Set(reflect.ValueOf(targetValue))
+ }
+ if destinationField.Type() == reflect.TypeOf(powerscale.NullableString{}) {
+ addr, ok := destinationField.Addr().Interface().(*powerscale.NullableString)
+ if !ok {
+ continue
+ }
+ addr.Set(&targetValue)
+ }
+ case basetypes.Int64Value:
+ intVal, ok := sourceValue.Field(i).Interface().(basetypes.Int64Value)
+ if !ok || intVal.IsNull() || intVal.IsUnknown() {
+ continue
+ }
+ if destinationField.Kind() == reflect.Int64 {
+ destinationField.Set(reflect.ValueOf(intVal.ValueInt64()))
+ }
+ if destinationField.Kind() == reflect.Ptr && destinationField.Type().Elem().Kind() == reflect.Int64 {
+ destinationField.Set(reflect.ValueOf(intVal.ValueInt64Pointer()))
+ }
+ if destinationField.Kind() == reflect.Int32 {
+ destinationField.Set(reflect.ValueOf(int32(intVal.ValueInt64())))
+ }
+ if destinationField.Kind() == reflect.Ptr && destinationField.Type().Elem().Kind() == reflect.Int32 {
+ val := int32(intVal.ValueInt64())
+ destinationField.Set(reflect.ValueOf(&val))
+ }
+ case basetypes.BoolValue:
+ boolVal, ok := sourceValue.Field(i).Interface().(basetypes.BoolValue)
+ if !ok || boolVal.IsNull() || boolVal.IsUnknown() {
+ continue
+ }
+ if destinationField.Kind() == reflect.Ptr {
+ destinationField.Set(reflect.ValueOf(boolVal.ValueBoolPointer()))
+ } else {
+ destinationField.Set(reflect.ValueOf(boolVal.ValueBool()))
+ }
+ case basetypes.ObjectValue:
+ objVal, ok := sourceValue.Field(i).Interface().(basetypes.ObjectValue)
+ if !ok || objVal.IsNull() || objVal.IsUnknown() {
+ continue
+ }
+ err := assignObjectToField(ctx, objVal, destinationField.Addr().Interface())
+ if err != nil {
+ return err
+ }
+ case basetypes.ListValue:
+ listVal, ok := sourceValue.Field(i).Interface().(basetypes.ListValue)
+ if !ok || listVal.IsNull() || listVal.IsUnknown() {
+ continue
+ }
+ err := assignListToField(ctx, listVal, destinationField.Addr().Interface())
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+ return nil
+}
+
+func assignObjectToField(ctx context.Context, source basetypes.ObjectValue, destination interface{}) error {
+ destElemVal := reflect.ValueOf(destination).Elem()
+ destElemType := destElemVal.Type()
+ targetObject := reflect.New(destElemType).Elem()
+ // if target is pointer to a pointer
+ if destElemVal.Kind() == reflect.Ptr {
+ destElemVal = reflect.ValueOf(destination).Elem().Elem()
+ destElemType = destElemVal.Type()
+ targetObject = reflect.New(destElemType).Elem()
+ }
+ attrMap := source.Attributes()
+ for key, val := range attrMap {
+ destinationField, err := getFieldByJSONTag(targetObject.Addr().Interface(), key)
+ if err != nil {
+ // skip current field
+ continue
+ }
+ if destinationField.IsValid() && destinationField.CanSet() {
+ switch val.Type(ctx) {
+ case basetypes.StringType{}:
+ stringVal, ok := val.(basetypes.StringValue)
+ if !ok || stringVal.IsNull() || stringVal.IsUnknown() {
+ continue
+ }
+ targetValue := stringVal.ValueString()
+ if destinationField.Kind() == reflect.Ptr && destinationField.Type().Elem().Kind() == reflect.String {
+ destinationField.Set(reflect.ValueOf(&targetValue))
+ }
+ if destinationField.Type().Kind() == reflect.String {
+ destinationField.Set(reflect.ValueOf(targetValue))
+ }
+ case basetypes.Int64Type{}:
+ intVal, ok := val.(basetypes.Int64Value)
+ if !ok || intVal.IsNull() || intVal.IsUnknown() {
+ continue
+ }
+ if destinationField.Kind() == reflect.Int64 {
+ destinationField.Set(reflect.ValueOf(intVal.ValueInt64()))
+ }
+ if destinationField.Kind() == reflect.Ptr && destinationField.Type().Elem().Kind() == reflect.Int64 {
+ destinationField.Set(reflect.ValueOf(intVal.ValueInt64Pointer()))
+ }
+ if destinationField.Kind() == reflect.Int32 {
+ destinationField.Set(reflect.ValueOf(int32(intVal.ValueInt64())))
+ }
+ if destinationField.Kind() == reflect.Ptr && destinationField.Type().Elem().Kind() == reflect.Int32 {
+ val := int32(intVal.ValueInt64())
+ destinationField.Set(reflect.ValueOf(&val))
+ }
+ case basetypes.BoolType{}:
+ boolVal, ok := val.(basetypes.BoolValue)
+ if !ok || boolVal.IsNull() || boolVal.IsUnknown() {
+ continue
+ }
+ if destinationField.Kind() == reflect.Ptr {
+ destinationField.Set(reflect.ValueOf(boolVal.ValueBoolPointer()))
+ } else {
+ destinationField.Set(reflect.ValueOf(boolVal.ValueBool()))
+ }
+ default:
+ typeString := val.Type(ctx).String()
+ if strings.HasPrefix(typeString, "types.ObjectType") {
+ objVal, ok := val.(basetypes.ObjectValue)
+ if !ok || objVal.IsNull() || objVal.IsUnknown() {
+ continue
+ }
+ err := assignObjectToField(ctx, objVal, destinationField.Addr().Interface())
+ if err != nil {
+ return err
+ }
+ } else if strings.HasPrefix(typeString, "types.ListType") {
+ listVal, ok := val.(basetypes.ListValue)
+ if !ok || listVal.IsNull() || listVal.IsUnknown() {
+ continue
+ }
+ err := assignListToField(ctx, listVal, destinationField.Addr().Interface())
+ if err != nil {
+ return err
+ }
+ }
+ }
+ }
+ }
+ destElemVal.Set(targetObject)
+ return nil
+}
+
+func assignListToField(ctx context.Context, source basetypes.ListValue, destination interface{}) error {
+ destVal := reflect.ValueOf(destination).Elem()
+ // type of element of slice
+ destType := destVal.Type()
+ // if target is pointer to a pointer
+ if destVal.Kind() == reflect.Ptr {
+ destVal = destVal.Elem()
+ destType = destVal.Type()
+ }
+ listLen := len(source.Elements())
+
+ listElemType := source.ElementType(ctx)
+ switch listElemType {
+ case basetypes.StringType{}:
+ tfsdk.ValueAs(ctx, source, destination)
+ case basetypes.Int64Type{}:
+ tfsdk.ValueAs(ctx, source, destination)
+ case basetypes.BoolType{}:
+ tfsdk.ValueAs(ctx, source, destination)
+ default:
+ targetList := reflect.MakeSlice(destType, listLen, listLen)
+ typeString := listElemType.String()
+ for i, listElem := range source.Elements() {
+ if strings.HasPrefix(typeString, "types.ListType") {
+ listVal, ok := listElem.(basetypes.ListValue)
+ if !ok || listVal.IsNull() || listVal.IsUnknown() {
+ continue
+ }
+ err := assignListToField(ctx, listVal, targetList.Index(i).Addr().Interface())
+ if err != nil {
+ return err
+ }
+ } else if strings.HasPrefix(typeString, "types.ObjectType") {
+ objVal, ok := listElem.(basetypes.ObjectValue)
+ if !ok || objVal.IsNull() || objVal.IsUnknown() {
+ continue
+ }
+ err := assignObjectToField(ctx, objVal, targetList.Index(i).Addr().Interface())
+ if err != nil {
+ return err
+ }
+ }
+ }
+ destVal.Set(targetList)
+ }
+ return nil
+}
+
+// getFieldByJSONTag get field by tag, input destination is pointer
+func getFieldByJSONTag(destination interface{}, tag string) (reflect.Value, error) {
+ destElemVal := reflect.ValueOf(destination).Elem()
+ destElemType := destElemVal.Type()
+
+ for i := 0; i < destElemType.NumField(); i++ {
+ field := destElemType.Field(i)
+ jsonTag := field.Tag.Get("json")
+ if strings.Contains(jsonTag, ",") {
+ jsonTag = strings.TrimSuffix(jsonTag, ",omitempty")
+ }
+ if jsonTag == tag {
+ return destElemVal.Field(i), nil
+ }
+ }
+
+ return reflect.Value{}, fmt.Errorf("field with tag %s not found in destination", tag)
+}
diff --git a/powerscale/helper/resource_helper_test.go b/powerscale/helper/resource_helper_test.go
new file mode 100644
index 00000000..76ca6437
--- /dev/null
+++ b/powerscale/helper/resource_helper_test.go
@@ -0,0 +1,137 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package helper
+
+import (
+ "context"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+type OpenapiStruct struct {
+ BoolPtr *bool `json:"bool_ptr,omitempty"`
+ BoolVal bool `json:"bool_val,omitempty"`
+ StringPtr *string `json:"string_ptr,omitempty"`
+ StringVal string `json:"string_val,omitempty"`
+ Int64Ptr *int64 `json:"int_64_ptr,omitempty"`
+ Int64Val int64 `json:"int_64_val,omitempty"`
+ NestedSlice []OpenapiChildStruct `json:"nested_slice,omitempty"`
+ NestedObject OpenapiChildSingleStruct `json:"nested_object,omitempty"`
+}
+
+type OpenapiChildStruct struct {
+ Str string `json:"str,omitempty"`
+}
+
+type OpenapiChildSingleStruct struct {
+ Strings []string `json:"strings,omitempty"`
+ Integers []int64 `json:"integers,omitempty"`
+ Structs []OpenapiChildStruct `json:"structs,omitempty"`
+ SingleStruct OpenapiGrandChildSingleStruct `json:"single_struct,omitempty"`
+}
+type OpenapiGrandChildSingleStruct struct {
+ String string `json:"str,omitempty"`
+}
+
+var fakeBool = true
+var fakeString = "fake_string"
+var fakeInt = int64(32)
+
+var openapiStructObj = OpenapiStruct{
+ BoolPtr: &fakeBool,
+ BoolVal: fakeBool,
+ StringPtr: &fakeString,
+ StringVal: fakeString,
+ Int64Ptr: &fakeInt,
+ Int64Val: fakeInt,
+ NestedSlice: []OpenapiChildStruct{{
+ Str: "fake_child_1",
+ }, {
+ Str: "fake_child_2",
+ }},
+ NestedObject: OpenapiChildSingleStruct{
+ Strings: []string{"1", "2", "3"},
+ Integers: []int64{1, 2, 3},
+ Structs: []OpenapiChildStruct{{
+ Str: "single_child_1",
+ }, {
+ Str: "single_child_2",
+ }},
+ },
+}
+
+type TfStruct struct {
+ BoolPtr types.Bool `tfsdk:"bool_ptr"`
+ BoolVal types.Bool `tfsdk:"bool_val"`
+ StringPtr types.String `tfsdk:"string_ptr"`
+ StringVal types.String `tfsdk:"string_val"`
+ Int64Ptr types.Int64 `tfsdk:"int_64_ptr"`
+ Int64Val types.Int64 `tfsdk:"int_64_val"`
+ NestedSlice types.List `tfsdk:"nested_slice"`
+ NestedObject types.Object `tfsdk:"nested_object"`
+}
+
+func Test_CopyFields(t *testing.T) {
+ testCopyTfObj := TfStruct{}
+ err := CopyFieldsToNonNestedModel(context.Background(), openapiStructObj, &testCopyTfObj)
+ assert.Equal(t, fakeBool, testCopyTfObj.BoolPtr.ValueBool())
+ assert.Equal(t, fakeString, testCopyTfObj.StringPtr.ValueString())
+ assert.Equal(t, fakeInt, testCopyTfObj.Int64Val.ValueInt64())
+ assert.Equal(t, 2, len(testCopyTfObj.NestedSlice.Elements()))
+ assert.False(t, testCopyTfObj.NestedObject.IsNull())
+ assert.Nil(t, err)
+}
+
+func Test_ReadFromState(t *testing.T) {
+ nestedAttrMap := map[string]attr.Type{
+ "strings": types.ListType{
+ ElemType: types.StringType,
+ },
+ }
+ nestedObj, _ := types.ListValueFrom(context.Background(), types.StringType, []string{"state1, state2, state3"})
+ nestedValueMap := map[string]attr.Value{
+ "strings": nestedObj,
+ }
+ obj, _ := types.ObjectValue(nestedAttrMap, nestedValueMap)
+ sliceAttrMap := map[string]attr.Type{
+ "str": types.StringType,
+ }
+ sliceAttrVal := map[string]attr.Value{
+ "str": types.StringValue("slice_1"),
+ }
+ sliceObj, _ := types.ObjectValue(sliceAttrMap, sliceAttrVal)
+ sliceObjs, _ := types.ListValue(types.ObjectType{
+ AttrTypes: sliceAttrMap,
+ }, []attr.Value{sliceObj})
+ tfStructObj := TfStruct{
+ BoolPtr: types.BoolValue(fakeBool),
+ BoolVal: types.BoolValue(fakeBool),
+ StringPtr: types.StringValue(fakeString),
+ StringVal: types.StringValue(fakeString),
+ Int64Ptr: types.Int64Value(fakeInt),
+ Int64Val: types.Int64Value(fakeInt),
+ NestedObject: obj,
+ NestedSlice: sliceObjs,
+ }
+ target := OpenapiStruct{}
+ err := ReadFromState(context.Background(), tfStructObj, &target)
+
+ assert.Nil(t, err)
+}
diff --git a/powerscale/helper/smb_share_helper.go b/powerscale/helper/smb_share_helper.go
new file mode 100644
index 00000000..8c05a9ed
--- /dev/null
+++ b/powerscale/helper/smb_share_helper.go
@@ -0,0 +1,49 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package helper
+
+import (
+ "context"
+ powerscale "dell/powerscale-go-client"
+ "terraform-provider-powerscale/client"
+)
+
+// DeleteSmbShare delete smb share
+func DeleteSmbShare(ctx context.Context, client *client.Client, shareID string) error {
+ _, err := client.PscaleOpenAPIClient.ProtocolsApi.DeleteProtocolsv7SmbShare(ctx, shareID).Execute()
+ return err
+}
+
+// CreateSmbShare create smb share
+func CreateSmbShare(ctx context.Context, client *client.Client, share powerscale.V7SmbShare) (*powerscale.Createv12SmbShareResponse, error) {
+ shareID, _, err := client.PscaleOpenAPIClient.ProtocolsApi.CreateProtocolsv7SmbShare(ctx).V7SmbShare(share).Execute()
+ return shareID, err
+}
+
+// GetSmbShare get smb share
+func GetSmbShare(ctx context.Context, client *client.Client, shareID string) (*powerscale.V7SmbSharesExtended, error) {
+ response, _, err := client.PscaleOpenAPIClient.ProtocolsApi.GetProtocolsv7SmbShare(ctx, shareID).Execute()
+ return response, err
+}
+
+// UpdateSmbShare update smb share
+func UpdateSmbShare(ctx context.Context, client *client.Client, shareID string, shareToUpdate powerscale.V7SmbShareExtendedExtended) error {
+ updateParam := client.PscaleOpenAPIClient.ProtocolsApi.UpdateProtocolsv7SmbShare(ctx, shareID)
+ _, err := updateParam.V7SmbShare(shareToUpdate).Execute()
+ return err
+}
diff --git a/powerscale/models/cluster.go b/powerscale/models/cluster.go
index 230e1c91..a283e866 100644
--- a/powerscale/models/cluster.go
+++ b/powerscale/models/cluster.go
@@ -1,3 +1,20 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package models
import "github.com/hashicorp/terraform-plugin-framework/types"
diff --git a/powerscale/models/smb_share.go b/powerscale/models/smb_share.go
new file mode 100644
index 00000000..e8c00e94
--- /dev/null
+++ b/powerscale/models/smb_share.go
@@ -0,0 +1,228 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import "github.com/hashicorp/terraform-plugin-framework/types"
+
+// SmbShareResource smb share schema attribute details.
+type SmbShareResource struct {
+ // ID of the smb share
+ ID types.String `tfsdk:"id"`
+ // Only enumerate files and folders the requesting user has access to.
+ AccessBasedEnumeration types.Bool `tfsdk:"access_based_enumeration"`
+ // Access-based enumeration on only the root directory of the share.
+ AccessBasedEnumerationRootOnly types.Bool `tfsdk:"access_based_enumeration_root_only"`
+ // Allow deletion of read-only files in the share.
+ AllowDeleteReadonly types.Bool `tfsdk:"allow_delete_readonly"`
+ // Allows users to execute files they have read rights for.
+ AllowExecuteAlways types.Bool `tfsdk:"allow_execute_always"`
+ // Allow automatic expansion of variables for home directories.
+ AllowVariableExpansion types.Bool `tfsdk:"allow_variable_expansion"`
+ // Automatically create home directories.
+ AutoCreateDirectory types.Bool `tfsdk:"auto_create_directory"`
+ // Share is visible in net view and the browse list.
+ Browsable types.Bool `tfsdk:"browsable"`
+ // Persistent open timeout for the share.
+ CaTimeout types.Int64 `tfsdk:"ca_timeout"`
+ // Specify the level of write-integrity on continuously available shares.
+ CaWriteIntegrity types.String `tfsdk:"ca_write_integrity"`
+ // Level of change notification alerts on the share.
+ ChangeNotify types.String `tfsdk:"change_notify"`
+ // Specify if persistent opens are allowed on the share.
+ ContinuouslyAvailable types.Bool `tfsdk:"continuously_available"`
+ // Create path if does not exist.
+ CreatePath types.Bool `tfsdk:"create_path"`
+ // Create permissions for new files and directories in share.
+ CreatePermissions types.String `tfsdk:"create_permissions"`
+ // Client-side caching policy for the shares.
+ CscPolicy types.String `tfsdk:"csc_policy"`
+ // Description for this SMB share.
+ Description types.String `tfsdk:"description"`
+ // Directory create mask bits.
+ DirectoryCreateMask types.Int64 `tfsdk:"directory_create_mask"`
+ // Directory create mode bits.
+ DirectoryCreateMode types.Int64 `tfsdk:"directory_create_mode"`
+ // File create mask bits.
+ FileCreateMask types.Int64 `tfsdk:"file_create_mask"`
+ // File create mode bits.
+ FileCreateMode types.Int64 `tfsdk:"file_create_mode"`
+ // Specifies the list of file extensions.
+ FileFilterExtensions types.List `tfsdk:"file_filter_extensions"`
+ // Specifies if filter list is for deny or allow. Default is deny.
+ FileFilterType types.String `tfsdk:"file_filter_type"`
+ // Enables file filtering on this zone.
+ FileFilteringEnabled types.Bool `tfsdk:"file_filtering_enabled"`
+ // Hide files and directories that begin with a period '.'.
+ HideDotFiles types.Bool `tfsdk:"hide_dot_files"`
+ // An ACL expressing which hosts are allowed access. A deny clause must be the final entry.
+ HostACL types.List `tfsdk:"host_acl"`
+ // Specify the condition in which user access is done as the guest account.
+ ImpersonateGuest types.String `tfsdk:"impersonate_guest"`
+ // User account to be used as guest account.
+ ImpersonateUser types.String `tfsdk:"impersonate_user"`
+ // Set the inheritable ACL on the share path.
+ InheritablePathACL types.Bool `tfsdk:"inheritable_path_acl"`
+ // Specifies the wchar_t starting point for automatic byte mangling.
+ MangleByteStart types.Int64 `tfsdk:"mangle_byte_start"`
+ // Character mangle map.
+ MangleMap types.List `tfsdk:"mangle_map"`
+ // Share name.
+ Name types.String `tfsdk:"name"`
+ // Support NTFS ACLs on files and directories.
+ NtfsACLSupport types.Bool `tfsdk:"ntfs_acl_support"`
+ // Support oplocks.
+ Oplocks types.Bool `tfsdk:"oplocks"`
+ // Path of share within /ifs.
+ Path types.String `tfsdk:"path"`
+ // Specifies an ordered list of permission modifications.
+ Permissions types.List `tfsdk:"permissions"`
+ // Allow account to run as root.
+ RunAsRoot types.List `tfsdk:"run_as_root"`
+ // Enables SMB3 encryption for the share.
+ Smb3EncryptionEnabled types.Bool `tfsdk:"smb3_encryption_enabled"`
+ // Enables sparse file.
+ SparseFile types.Bool `tfsdk:"sparse_file"`
+ // Specifies if persistent opens would do strict lockout on the share.
+ StrictCaLockout types.Bool `tfsdk:"strict_ca_lockout"`
+ // Handle SMB flush operations.
+ StrictFlush types.Bool `tfsdk:"strict_flush"`
+ // Specifies whether byte range locks contend against SMB I/O.
+ StrictLocking types.Bool `tfsdk:"strict_locking"`
+ // Name of the access zone to which to move this SMB share.
+ Zone types.String `tfsdk:"zone"`
+ // Numeric ID of the access zone which contains this SMB share.
+ Zid types.Int64 `tfsdk:"zid"`
+}
+
+// V1SmbSharePermission Specifies properties for an Access Control Entry.
+type V1SmbSharePermission struct {
+ // Specifies the file system rights that are allowed or denied.
+ Permission types.String `tfsdk:"permission"`
+ // Determines whether the permission is allowed or denied.
+ PermissionType types.String `tfsdk:"permission_type"`
+ //
+ Trustee V1AuthAccessAccessItemFileGroup `tfsdk:"trustee"`
+}
+
+// SmbShareDatasource holds smb share datasource schema attribute details.
+type SmbShareDatasource struct {
+ ID types.String `tfsdk:"id"`
+ SmbShares []SmbShareDatasourceEntity `tfsdk:"smb_shares"`
+ SmbSharesFilter *SmbShareDatasourceFilter `tfsdk:"filter"`
+}
+
+// SmbShareDatasourceFilter holds filter conditions
+type SmbShareDatasourceFilter struct {
+ // supported by api
+ Sort types.String `tfsdk:"sort"`
+ Zone types.String `tfsdk:"zone"`
+ Resume types.String `tfsdk:"resume"`
+ ResolveNames types.Bool `tfsdk:"resolve_names"`
+ Limit types.Int64 `tfsdk:"limit"`
+ Offset types.Int64 `tfsdk:"offset"`
+ Scope types.String `tfsdk:"scope"`
+ Dir types.String `tfsdk:"dir"`
+ // custom name list
+ Names []types.String `tfsdk:"names"`
+}
+
+// SmbShareDatasourceEntity struct for SmbShareDatasource
+type SmbShareDatasourceEntity struct {
+ // Only enumerate files and folders the requesting user has access to.
+ AccessBasedEnumeration types.Bool `tfsdk:"access_based_enumeration"`
+ // Access-based enumeration on only the root directory of the share.
+ AccessBasedEnumerationRootOnly types.Bool `tfsdk:"access_based_enumeration_root_only"`
+ // Allow deletion of read-only files in the share.
+ AllowDeleteReadonly types.Bool `tfsdk:"allow_delete_readonly"`
+ // Allows users to execute files they have read rights for.
+ AllowExecuteAlways types.Bool `tfsdk:"allow_execute_always"`
+ // Allow automatic expansion of variables for home directories.
+ AllowVariableExpansion types.Bool `tfsdk:"allow_variable_expansion"`
+ // Automatically create home directories.
+ AutoCreateDirectory types.Bool `tfsdk:"auto_create_directory"`
+ // Share is visible in net view and the browse list.
+ Browsable types.Bool `tfsdk:"browsable"`
+ // Persistent open timeout for the share.
+ CaTimeout types.Int64 `tfsdk:"ca_timeout"`
+ // Specify the level of write-integrity on continuously available shares.
+ CaWriteIntegrity types.String `tfsdk:"ca_write_integrity"`
+ // Level of change notification alerts on the share.
+ ChangeNotify types.String `tfsdk:"change_notify"`
+ // Specify if persistent opens are allowed on the share.
+ ContinuouslyAvailable types.Bool `tfsdk:"continuously_available"`
+ // Create permissions for new files and directories in share.
+ CreatePermissions types.String `tfsdk:"create_permissions"`
+ // Client-side caching policy for the shares.
+ CscPolicy types.String `tfsdk:"csc_policy"`
+ // Description for this SMB share.
+ Description types.String `tfsdk:"description"`
+ // Directory create mask bits.
+ DirectoryCreateMask types.Int64 `tfsdk:"directory_create_mask"`
+ // Directory create mode bits.
+ DirectoryCreateMode types.Int64 `tfsdk:"directory_create_mode"`
+ // File create mask bits.
+ FileCreateMask types.Int64 `tfsdk:"file_create_mask"`
+ // File create mode bits.
+ FileCreateMode types.Int64 `tfsdk:"file_create_mode"`
+ // Specifies the list of file extensions.
+ FileFilterExtensions types.List `tfsdk:"file_filter_extensions"`
+ // Specifies if filter list is for deny or allow. Default is deny.
+ FileFilterType types.String `tfsdk:"file_filter_type"`
+ // Enables file filtering on this zone.
+ FileFilteringEnabled types.Bool `tfsdk:"file_filtering_enabled"`
+ // Hide files and directories that begin with a period '.'.
+ HideDotFiles types.Bool `tfsdk:"hide_dot_files"`
+ // An ACL expressing which hosts are allowed access. A deny clause must be the final entry.
+ HostACL types.List `tfsdk:"host_acl"`
+ // Share ID.
+ ID types.String `tfsdk:"id"`
+ // Specify the condition in which user access is done as the guest account.
+ ImpersonateGuest types.String `tfsdk:"impersonate_guest"`
+ // User account to be used as guest account.
+ ImpersonateUser types.String `tfsdk:"impersonate_user"`
+ // Set the inheritable ACL on the share path.
+ InheritablePathACL types.Bool `tfsdk:"inheritable_path_acl"`
+ // Specifies the wchar_t starting point for automatic byte mangling.
+ MangleByteStart types.Int64 `tfsdk:"mangle_byte_start"`
+ // Character mangle map.
+ MangleMap types.List `tfsdk:"mangle_map"`
+ // Share name.
+ Name types.String `tfsdk:"name"`
+ // Support NTFS ACLs on files and directories.
+ NtfsACLSupport types.Bool `tfsdk:"ntfs_acl_support"`
+ // Support oplocks.
+ Oplocks types.Bool `tfsdk:"oplocks"`
+ // Path of share within /ifs.
+ Path types.String `tfsdk:"path"`
+ // Specifies an ordered list of permission modifications.
+ Permissions []V1SmbSharePermission `tfsdk:"permissions"`
+ // Allow account to run as root.
+ RunAsRoot []V1AuthAccessAccessItemFileGroup `tfsdk:"run_as_root"`
+ // Enables SMB3 encryption for the share.
+ Smb3EncryptionEnabled types.Bool `tfsdk:"smb3_encryption_enabled"`
+ // Enables sparse file.
+ SparseFile types.Bool `tfsdk:"sparse_file"`
+ // Specifies if persistent opens would do strict lockout on the share.
+ StrictCaLockout types.Bool `tfsdk:"strict_ca_lockout"`
+ // Handle SMB flush operations.
+ StrictFlush types.Bool `tfsdk:"strict_flush"`
+ // Specifies whether byte range locks contend against SMB I/O.
+ StrictLocking types.Bool `tfsdk:"strict_locking"`
+ // Numeric ID of the access zone which contains this SMB share
+ Zid types.Int64 `tfsdk:"zid"`
+}
diff --git a/powerscale/provider/cluster_data_source.go b/powerscale/provider/cluster_data_source.go
index f324e978..1d7a77f5 100644
--- a/powerscale/provider/cluster_data_source.go
+++ b/powerscale/provider/cluster_data_source.go
@@ -1,3 +1,20 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
package provider
import (
diff --git a/powerscale/provider/provider.go b/powerscale/provider/provider.go
index 51a88e7c..bab1c75a 100644
--- a/powerscale/provider/provider.go
+++ b/powerscale/provider/provider.go
@@ -180,6 +180,7 @@ func (p *PscaleProvider) Configure(ctx context.Context, req provider.ConfigureRe
func (p *PscaleProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewAccessZoneResource,
+ NewSmbShareResource,
}
}
@@ -190,6 +191,7 @@ func (p *PscaleProvider) DataSources(ctx context.Context) []func() datasource.Da
NewAccessZoneDataSource,
NewClusterDataSource,
NewUserDataSource,
+ NewSmbShareDataSource,
}
}
diff --git a/powerscale/provider/provider_test.go b/powerscale/provider/provider_test.go
index 934eca10..329369b3 100644
--- a/powerscale/provider/provider_test.go
+++ b/powerscale/provider/provider_test.go
@@ -19,15 +19,13 @@ package provider
import (
"fmt"
- "log"
- "os"
- "testing"
-
. "github.com/bytedance/mockey"
-
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/joho/godotenv"
+ "log"
+ "os"
+ "testing"
)
// testAccProtoV6ProviderFactories are used to instantiate a provider during
diff --git a/powerscale/provider/smb_share_data_source.go b/powerscale/provider/smb_share_data_source.go
new file mode 100644
index 00000000..12b08a02
--- /dev/null
+++ b/powerscale/provider/smb_share_data_source.go
@@ -0,0 +1,503 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package provider
+
+import (
+ "context"
+ powerscale "dell/powerscale-go-client"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "terraform-provider-powerscale/client"
+ "terraform-provider-powerscale/powerscale/helper"
+ "terraform-provider-powerscale/powerscale/models"
+)
+
+var (
+ _ datasource.DataSource = &SmbShareDataSource{}
+ _ datasource.DataSourceWithConfigure = &SmbShareDataSource{}
+)
+
+// NewSmbShareDataSource returns the SmbShare data source object.
+func NewSmbShareDataSource() datasource.DataSource {
+ return &SmbShareDataSource{}
+}
+
+// SmbShareDataSource defines the data source implementation.
+type SmbShareDataSource struct {
+ client *client.Client
+}
+
+// Metadata describes the data source arguments.
+func (d *SmbShareDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_smb_share"
+}
+
+// Schema describes the data source arguments.
+func (d *SmbShareDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "Data source for reading SMB Shares in PowerScale array.",
+ Description: "Data source for reading SMB Shares in PowerScale array.",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Placeholder for acc testing",
+ Computed: true,
+ },
+ "smb_shares": schema.ListNestedAttribute{
+ Computed: true,
+ Description: "List of smb shares",
+ MarkdownDescription: "List of smb shares",
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "access_based_enumeration": schema.BoolAttribute{
+ Description: "Only enumerate files and folders the requesting user has access to.",
+ MarkdownDescription: "Only enumerate files and folders the requesting user has access to.",
+ Computed: true,
+ },
+ "access_based_enumeration_root_only": schema.BoolAttribute{
+ Description: "Access-based enumeration on only the root directory of the share.",
+ MarkdownDescription: "Access-based enumeration on only the root directory of the share.",
+ Computed: true,
+ },
+ "allow_delete_readonly": schema.BoolAttribute{
+ Description: "Allow deletion of read-only files in the share.",
+ MarkdownDescription: "Allow deletion of read-only files in the share.",
+ Computed: true,
+ },
+ "allow_execute_always": schema.BoolAttribute{
+ Description: "Allows users to execute files they have read rights for.",
+ MarkdownDescription: "Allows users to execute files they have read rights for.",
+ Computed: true,
+ },
+ "allow_variable_expansion": schema.BoolAttribute{
+ Description: "Allow automatic expansion of variables for home directories.",
+ MarkdownDescription: "Allow automatic expansion of variables for home directories.",
+ Computed: true,
+ },
+ "auto_create_directory": schema.BoolAttribute{
+ Description: "Automatically create home directories.",
+ MarkdownDescription: "Automatically create home directories.",
+ Computed: true,
+ },
+ "browsable": schema.BoolAttribute{
+ Description: "Share is visible in net view and the browse list.",
+ MarkdownDescription: "Share is visible in net view and the browse list.",
+ Computed: true,
+ },
+ "ca_timeout": schema.Int64Attribute{
+ Description: "Persistent open timeout for the share.",
+ MarkdownDescription: "Persistent open timeout for the share.",
+ Computed: true,
+ },
+ "ca_write_integrity": schema.StringAttribute{
+ Description: "Specify the level of write-integrity on continuously available shares.",
+ MarkdownDescription: "Specify the level of write-integrity on continuously available shares.",
+ Computed: true,
+ },
+ "change_notify": schema.StringAttribute{
+ Description: "Level of change notification alerts on the share.",
+ MarkdownDescription: "Level of change notification alerts on the share.",
+ Computed: true,
+ },
+ "continuously_available": schema.BoolAttribute{
+ Description: "Specify if persistent opens are allowed on the share.",
+ MarkdownDescription: "Specify if persistent opens are allowed on the share.",
+ Computed: true,
+ },
+ "create_permissions": schema.StringAttribute{
+ Description: "Create permissions for new files and directories in share.",
+ MarkdownDescription: "Create permissions for new files and directories in share.",
+ Computed: true,
+ },
+ "csc_policy": schema.StringAttribute{
+ Description: "Client-side caching policy for the shares.",
+ MarkdownDescription: "Client-side caching policy for the shares.",
+ Computed: true,
+ },
+ "description": schema.StringAttribute{
+ Description: "Description for this SMB share.",
+ MarkdownDescription: "Description for this SMB share.",
+ Computed: true,
+ },
+ "directory_create_mask": schema.Int64Attribute{
+ Description: "Directory create mask bits.",
+ MarkdownDescription: "Directory create mask bits.",
+ Computed: true,
+ },
+ "directory_create_mode": schema.Int64Attribute{
+ Description: "Directory create mode bits.",
+ MarkdownDescription: "Directory create mode bits.",
+ Computed: true,
+ },
+ "file_create_mask": schema.Int64Attribute{
+ Description: "File create mask bits.",
+ MarkdownDescription: "File create mask bits.",
+ Computed: true,
+ },
+ "file_create_mode": schema.Int64Attribute{
+ Description: "File create mode bits.",
+ MarkdownDescription: "File create mode bits.",
+ Computed: true,
+ },
+ "file_filter_extensions": schema.ListAttribute{
+ Description: "Specifies the list of file extensions.",
+ MarkdownDescription: "Specifies the list of file extensions.",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "file_filter_type": schema.StringAttribute{
+ Description: "Specifies if filter list is for deny or allow. Default is deny.",
+ MarkdownDescription: "Specifies if filter list is for deny or allow. Default is deny.",
+ Computed: true,
+ },
+ "file_filtering_enabled": schema.BoolAttribute{
+ Description: "Enables file filtering on this zone.",
+ MarkdownDescription: "Enables file filtering on this zone.",
+ Computed: true,
+ },
+ "hide_dot_files": schema.BoolAttribute{
+ Description: "Hide files and directories that begin with a period '.'.",
+ MarkdownDescription: "Hide files and directories that begin with a period '.'.",
+ Computed: true,
+ },
+ "host_acl": schema.ListAttribute{
+ Description: "An ACL expressing which hosts are allowed access. A deny clause must be the final entry.",
+ MarkdownDescription: "An ACL expressing which hosts are allowed access. A deny clause must be the final entry.",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "id": schema.StringAttribute{
+ Description: "Share ID.",
+ MarkdownDescription: "Share ID.",
+ Computed: true,
+ },
+ "impersonate_guest": schema.StringAttribute{
+ Description: "Specify the condition in which user access is done as the guest account.",
+ MarkdownDescription: "Specify the condition in which user access is done as the guest account.",
+ Computed: true,
+ },
+ "impersonate_user": schema.StringAttribute{
+ Description: "User account to be used as guest account.",
+ MarkdownDescription: "User account to be used as guest account.",
+ Computed: true,
+ },
+ "inheritable_path_acl": schema.BoolAttribute{
+ Description: "Set the inheritable ACL on the share path.",
+ MarkdownDescription: "Set the inheritable ACL on the share path.",
+ Computed: true,
+ },
+ "mangle_byte_start": schema.Int64Attribute{
+ Description: "Specifies the wchar_t starting point for automatic byte mangling.",
+ MarkdownDescription: "Specifies the wchar_t starting point for automatic byte mangling.",
+ Computed: true,
+ },
+ "mangle_map": schema.ListAttribute{
+ Description: "Character mangle map.",
+ MarkdownDescription: "Character mangle map.",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "name": schema.StringAttribute{
+ Description: "Share name.",
+ MarkdownDescription: "Share name.",
+ Computed: true,
+ },
+ "ntfs_acl_support": schema.BoolAttribute{
+ Description: "Support NTFS ACLs on files and directories.",
+ MarkdownDescription: "Support NTFS ACLs on files and directories.",
+ Computed: true,
+ },
+ "oplocks": schema.BoolAttribute{
+ Description: "Support oplocks.",
+ MarkdownDescription: "Support oplocks.",
+ Computed: true,
+ },
+ "path": schema.StringAttribute{
+ Description: "Path of share within /ifs.",
+ MarkdownDescription: "Path of share within /ifs.",
+ Computed: true,
+ },
+ "permissions": schema.ListNestedAttribute{
+ Description: "Specifies an ordered list of permission modifications.",
+ MarkdownDescription: "Specifies an ordered list of permission modifications.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "permission": schema.StringAttribute{
+ Description: "Specifies the file system rights that are allowed or denied.",
+ MarkdownDescription: "Specifies the file system rights that are allowed or denied.",
+ Computed: true,
+ },
+ "permission_type": schema.StringAttribute{
+ Description: "Determines whether the permission is allowed or denied.",
+ MarkdownDescription: "Determines whether the permission is allowed or denied.",
+ Computed: true,
+ },
+ "trustee": schema.SingleNestedAttribute{
+ Description: "Specifies the persona of the file group.",
+ MarkdownDescription: "Specifies the persona of the file group.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.",
+ MarkdownDescription: "Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.",
+ Computed: true,
+ },
+ "name": schema.StringAttribute{
+ Description: "Specifies the persona name, which must be combined with a type.",
+ MarkdownDescription: "Specifies the persona name, which must be combined with a type.",
+ Computed: true,
+ },
+ "type": schema.StringAttribute{
+ Description: "Specifies the type of persona, which must be combined with a name.",
+ MarkdownDescription: "Specifies the type of persona, which must be combined with a name.",
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ "run_as_root": schema.ListNestedAttribute{
+ Description: "Allow account to run as root.",
+ MarkdownDescription: "Allow account to run as root.",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.",
+ MarkdownDescription: "Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.",
+ Computed: true,
+ },
+ "name": schema.StringAttribute{
+ Description: "Specifies the persona name, which must be combined with a type.",
+ MarkdownDescription: "Specifies the persona name, which must be combined with a type.",
+ Computed: true,
+ },
+ "type": schema.StringAttribute{
+ Description: "Specifies the type of persona, which must be combined with a name.",
+ MarkdownDescription: "Specifies the type of persona, which must be combined with a name.",
+ Computed: true,
+ },
+ },
+ },
+ },
+ "smb3_encryption_enabled": schema.BoolAttribute{
+ Description: "Enables SMB3 encryption for the share.",
+ MarkdownDescription: "Enables SMB3 encryption for the share.",
+ Computed: true,
+ },
+ "sparse_file": schema.BoolAttribute{
+ Description: "Enables sparse file.",
+ MarkdownDescription: "Enables sparse file.",
+ Computed: true,
+ },
+ "strict_ca_lockout": schema.BoolAttribute{
+ Description: "Specifies if persistent opens would do strict lockout on the share.",
+ MarkdownDescription: "Specifies if persistent opens would do strict lockout on the share.",
+ Computed: true,
+ },
+ "strict_flush": schema.BoolAttribute{
+ Description: "Handle SMB flush operations.",
+ MarkdownDescription: "Handle SMB flush operations.",
+ Computed: true,
+ },
+ "strict_locking": schema.BoolAttribute{
+ Description: "Specifies whether byte range locks contend against SMB I/O.",
+ MarkdownDescription: "Specifies whether byte range locks contend against SMB I/O.",
+ Computed: true,
+ },
+ "zid": schema.Int64Attribute{
+ Description: "Numeric ID of the access zone which contains this SMB share",
+ MarkdownDescription: "Numeric ID of the access zone which contains this SMB share",
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ Blocks: map[string]schema.Block{
+ "filter": schema.SingleNestedBlock{
+ Attributes: map[string]schema.Attribute{
+ "names": schema.SetAttribute{
+ Description: "Names to filter smb shares.",
+ MarkdownDescription: "Names to filter smb shares.",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "sort": schema.StringAttribute{
+ Description: "The field that will be used for sorting.",
+ MarkdownDescription: "The field that will be used for sorting.",
+ Optional: true,
+ },
+ "zone": schema.StringAttribute{
+ Description: "Specifies which access zone to use.",
+ MarkdownDescription: "Specifies which access zone to use.",
+ Optional: true,
+ },
+ "resume": schema.StringAttribute{
+ Description: "Continue returning results from previous call using this token " +
+ "(token should come from the previous call, resume cannot be used with other options).",
+ MarkdownDescription: "Continue returning results from previous call using this token " +
+ "(token should come from the previous call, resume cannot be used with other options).",
+ Optional: true,
+ },
+ "resolve_names": schema.BoolAttribute{
+ Description: "If true, resolve group and user names in personas.",
+ MarkdownDescription: "If true, resolve group and user names in personas.",
+ Optional: true,
+ },
+ "limit": schema.Int64Attribute{
+ Description: "Return no more than this many results at once (see resume).",
+ MarkdownDescription: "Return no more than this many results at once (see resume).",
+ Optional: true,
+ },
+ "offset": schema.Int64Attribute{
+ Description: "The position of the first item returned for a paginated query within the full result set.",
+ MarkdownDescription: "The position of the first item returned for a paginated query within the full result set.",
+ Optional: true,
+ },
+ "scope": schema.StringAttribute{
+ Description: "If specified as \"effective\" or not specified, all fields are returned. " +
+ "If specified as \"user\", only fields with non-default values are shown. If specified as \"default\", the original values are returned.",
+ MarkdownDescription: "If specified as \"effective\" or not specified, all fields are returned. " +
+ "If specified as \"user\", only fields with non-default values are shown. If specified as \"default\", the original values are returned.",
+ Optional: true,
+ },
+ "dir": schema.StringAttribute{
+ Description: "The direction of the sort.",
+ MarkdownDescription: "The direction of the sort.",
+ Optional: true,
+ },
+ },
+ },
+ },
+ }
+}
+
+// Configure configures the resource.
+func (d *SmbShareDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ // Prevent panic if the provider has not been configured.
+ if req.ProviderData == nil {
+ return
+ }
+
+ pscaleClient, ok := req.ProviderData.(*client.Client)
+
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+
+ return
+ }
+
+ d.client = pscaleClient
+}
+
+// Read reads data from the data source.
+func (d *SmbShareDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ tflog.Info(ctx, "Reading Smb Share data source ")
+ var sharesPlan models.SmbShareDatasource
+ var sharesState models.SmbShareDatasource
+ // Read Terraform configuration data into the model
+ resp.Diagnostics.Append(req.Config.Get(ctx, &sharesPlan)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ var shareNames []types.String
+ listSmbParam := d.client.PscaleOpenAPIClient.ProtocolsApi.ListProtocolsv7SmbShares(ctx)
+ if sharesPlan.SmbSharesFilter != nil {
+ shareNames = sharesPlan.SmbSharesFilter.Names
+
+ // handle api filter
+ listSmbParam.Resume(sharesPlan.SmbSharesFilter.Resume.ValueString())
+ listSmbParam.Zone(sharesPlan.SmbSharesFilter.Zone.ValueString())
+ listSmbParam.Dir(sharesPlan.SmbSharesFilter.Dir.ValueString())
+ listSmbParam.Scope(sharesPlan.SmbSharesFilter.Scope.ValueString())
+ listSmbParam.Sort(sharesPlan.SmbSharesFilter.Sort.ValueString())
+ if !sharesPlan.SmbSharesFilter.ResolveNames.IsNull() {
+ listSmbParam.ResolveNames(sharesPlan.SmbSharesFilter.ResolveNames.ValueBool())
+ }
+ if !sharesPlan.SmbSharesFilter.Limit.IsNull() {
+ listSmbParam.Limit(int32(sharesPlan.SmbSharesFilter.Limit.ValueInt64()))
+ }
+ }
+ smbShares, _, err := listSmbParam.Execute()
+ if err != nil {
+ resp.Diagnostics.AddError("Error reading smb share datasource",
+ fmt.Sprintf("Could not list smb shares with error: %s", err.Error()))
+ return
+ }
+ totalSmbShares := smbShares.Shares
+ for smbShares.Resume != nil && (sharesPlan.SmbSharesFilter == nil || sharesPlan.SmbSharesFilter.Limit.IsNull()) {
+ resumeSmbParam := d.client.PscaleOpenAPIClient.ProtocolsApi.ListProtocolsv7SmbShares(ctx).Resume(*smbShares.Resume)
+ smbShares, _, err = resumeSmbParam.Execute()
+ if err != nil {
+ resp.Diagnostics.AddError("Error reading smb share datasource plan",
+ fmt.Sprintf("Could not list smb shares with error: %s", err.Error()))
+ return
+ }
+ totalSmbShares = append(totalSmbShares, smbShares.Shares...)
+ }
+
+ // if names are specified filter locally
+ var filteredShares []models.SmbShareDatasourceEntity
+ if len(shareNames) > 0 {
+ sharesMap := make(map[string]powerscale.V7SmbShareExtended)
+ for _, s := range totalSmbShares {
+ sharesMap[s.Name] = s
+ }
+ for _, name := range shareNames {
+ if specifiedShare, ok := sharesMap[name.ValueString()]; ok {
+ entity := models.SmbShareDatasourceEntity{}
+ err := helper.CopyFields(ctx, specifiedShare, &entity)
+ if err != nil {
+ resp.Diagnostics.AddError("Error reading smb share datasource plan",
+ fmt.Sprintf("Could not list smb shares with error: %s", err.Error()))
+ return
+ }
+ filteredShares = append(filteredShares, entity)
+ }
+ }
+ } else {
+ entity := models.SmbShareDatasourceEntity{}
+ for _, share := range totalSmbShares {
+ err := helper.CopyFields(ctx, share, &entity)
+ if err != nil {
+ resp.Diagnostics.AddError("Error reading smb share datasource plan",
+ fmt.Sprintf("Could not list smb shares with error: %s", err.Error()))
+ return
+ }
+ filteredShares = append(filteredShares, entity)
+ }
+ }
+ //check if there is any error while getting the port group
+ sharesState.ID = types.StringValue("1")
+ sharesState.SmbSharesFilter = sharesPlan.SmbSharesFilter
+ sharesState.SmbShares = filteredShares
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &sharesState)...)
+}
diff --git a/powerscale/provider/smb_share_data_source_test.go b/powerscale/provider/smb_share_data_source_test.go
new file mode 100644
index 00000000..80b5076c
--- /dev/null
+++ b/powerscale/provider/smb_share_data_source_test.go
@@ -0,0 +1,170 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package provider
+
+import (
+ powerscale "dell/powerscale-go-client"
+ "fmt"
+ . "github.com/bytedance/mockey"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "regexp"
+ "terraform-provider-powerscale/powerscale/helper"
+ "testing"
+)
+
+func TestAccSmbShareDatasource(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ //Read testing
+ {
+ Config: ProviderConfig + SmbShareDatasourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.powerscale_smb_share.share_datasource_test", "smb_shares.#", "1"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccSmbShareDatasourceGetAll(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ //Read testing
+ {
+ Config: ProviderConfig + SmbShareAllDatasourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.powerscale_smb_share.share_datasource_test_all", "filter.#", "0"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccSmbShareDatasourcePagination(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ //Read testing
+ {
+ PreConfig: func() {
+ resume := "1"
+ shares := powerscale.V7SmbShares{
+ Digest: nil,
+ Resume: &resume,
+ Shares: nil,
+ Total: nil,
+ }
+ if FunctionMocker != nil {
+ FunctionMocker.Release()
+ }
+ FunctionMocker = Mock(GetMethod(powerscale.ApiListProtocolsv7SmbSharesRequest{}, "Execute")).Return(&shares, nil, nil).Build()
+ FunctionMocker.When(func() bool {
+ shares := powerscale.V7SmbShares{
+ Digest: nil,
+ Resume: nil,
+ Shares: []powerscale.V7SmbShareExtended{{Id: shareName}},
+ Total: nil,
+ }
+ if FunctionMocker.MockTimes() > 0 {
+ FunctionMocker.UnPatch()
+ FunctionMocker = Mock(GetMethod(powerscale.ApiListProtocolsv7SmbSharesRequest{}, "Execute")).Return(&shares, nil, nil).Build()
+ }
+ return FunctionMocker.MockTimes() == 0
+ })
+ },
+ Config: ProviderConfig + SmbShareAllDatasourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.powerscale_smb_share.share_datasource_test_all", "filter.#", "0"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccSmbShareDatasourceErrorCopyFields(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ //Read testing
+ {
+ PreConfig: func() {
+ FunctionMocker = Mock(helper.CopyFields).Return(fmt.Errorf("mock error")).Build()
+ },
+ Config: ProviderConfig + SmbShareAllDatasourceConfig,
+ ExpectError: regexp.MustCompile("mock error"),
+ },
+ },
+ })
+}
+
+var SmbShareAllDatasourceConfig = `
+resource "powerscale_smb_share" "share_resource_test" {
+ auto_create_directory = true
+ name = "%s"
+ path = "/ifs/%s"
+ permissions = [
+ {
+ permission = "full"
+ permission_type = "allow"
+ trustee = {
+ id = "SID:S-1-1-0",
+ name = "Everyone",
+ type = "wellknown"
+ }
+ }
+ ]
+}
+
+data "powerscale_smb_share" "share_datasource_test_all" {}
+`
+
+var SmbShareDatasourceConfig = fmt.Sprintf(`
+resource "powerscale_smb_share" "share_resource_test" {
+ auto_create_directory = true
+ name = "%s"
+ path = "/ifs/%s"
+ permissions = [
+ {
+ permission = "full"
+ permission_type = "allow"
+ trustee = {
+ id = "SID:S-1-1-0",
+ name = "Everyone",
+ type = "wellknown"
+ }
+ }
+ ]
+}
+
+data "powerscale_smb_share" "share_datasource_test" {
+ filter {
+ resolve_names = true
+ names = ["%s"]
+ limit = 1
+ }
+ depends_on = [
+ powerscale_smb_share.share_resource_test
+ ]
+}
+`, shareName, shareName, shareName)
diff --git a/powerscale/provider/smb_share_resource.go b/powerscale/provider/smb_share_resource.go
new file mode 100644
index 00000000..6459847a
--- /dev/null
+++ b/powerscale/provider/smb_share_resource.go
@@ -0,0 +1,636 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package provider
+
+import (
+ "context"
+ powerscale "dell/powerscale-go-client"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+ "terraform-provider-powerscale/client"
+ "terraform-provider-powerscale/powerscale/helper"
+ "terraform-provider-powerscale/powerscale/models"
+)
+
+// SmbShareResource creates a new resource.
+type SmbShareResource struct {
+ client *client.Client
+}
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ resource.Resource = &SmbShareResource{}
+ _ resource.ResourceWithConfigure = &SmbShareResource{}
+ _ resource.ResourceWithImportState = &SmbShareResource{}
+)
+
+// NewSmbShareResource is a helper function to simplify the provider implementation.
+func NewSmbShareResource() resource.Resource {
+ return &SmbShareResource{}
+}
+
+// Metadata describes the resource arguments.
+func (r SmbShareResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_smb_share"
+}
+
+// Schema describes the resource arguments.
+func (r *SmbShareResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "Resource for managing SMB Shares in PowerScale array.",
+ Description: "Resource for managing SMB Shares in PowerScale array.",
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "The ID of the smb share.",
+ MarkdownDescription: "The ID of the smb share.",
+ Computed: true,
+ },
+ "access_based_enumeration": schema.BoolAttribute{
+ Description: "Only enumerate files and folders the requesting user has access to.",
+ MarkdownDescription: "Only enumerate files and folders the requesting user has access to.",
+ Optional: true,
+ Computed: true,
+ },
+ "access_based_enumeration_root_only": schema.BoolAttribute{
+ Description: "Access-based enumeration on only the root directory of the share.",
+ MarkdownDescription: "Access-based enumeration on only the root directory of the share.",
+ Optional: true,
+ Computed: true,
+ },
+ "allow_delete_readonly": schema.BoolAttribute{
+ Description: "Allow deletion of read-only files in the share.",
+ MarkdownDescription: "Allow deletion of read-only files in the share.",
+ Optional: true,
+ Computed: true,
+ },
+ "allow_execute_always": schema.BoolAttribute{
+ Description: "Allows users to execute files they have read rights for.",
+ MarkdownDescription: "Allows users to execute files they have read rights for.",
+ Optional: true,
+ Computed: true,
+ },
+ "allow_variable_expansion": schema.BoolAttribute{
+ Description: "Allow automatic expansion of variables for home directories.",
+ MarkdownDescription: "Allow automatic expansion of variables for home directories.",
+ Optional: true,
+ Computed: true,
+ },
+ "auto_create_directory": schema.BoolAttribute{
+ Description: "Automatically create home directories.",
+ MarkdownDescription: "Automatically create home directories.",
+ Optional: true,
+ Computed: true,
+ },
+ "browsable": schema.BoolAttribute{
+ Description: "Share is visible in net view and the browse list.",
+ MarkdownDescription: "Share is visible in net view and the browse list.",
+ Optional: true,
+ Computed: true,
+ },
+ "ca_timeout": schema.Int64Attribute{
+ Description: "Persistent open timeout for the share.",
+ MarkdownDescription: "Persistent open timeout for the share.",
+ Optional: true,
+ Computed: true,
+ },
+ "ca_write_integrity": schema.StringAttribute{
+ Description: "Specify the level of write-integrity on continuously available shares.",
+ MarkdownDescription: "Specify the level of write-integrity on continuously available shares.",
+ Optional: true,
+ Computed: true,
+ },
+ "change_notify": schema.StringAttribute{
+ Description: "Level of change notification alerts on the share.",
+ MarkdownDescription: "Level of change notification alerts on the share.",
+ Optional: true,
+ Computed: true,
+ },
+ "continuously_available": schema.BoolAttribute{
+ Description: "Specify if persistent opens are allowed on the share.",
+ MarkdownDescription: "Specify if persistent opens are allowed on the share.",
+ Computed: true,
+ },
+ "create_path": schema.BoolAttribute{
+ Description: "Create path if does not exist.",
+ MarkdownDescription: "Create path if does not exist.",
+ Optional: true,
+ },
+ "create_permissions": schema.StringAttribute{
+ Description: "Create permissions for new files and directories in share.",
+ MarkdownDescription: "Create permissions for new files and directories in share.",
+ Optional: true,
+ Computed: true,
+ },
+ "csc_policy": schema.StringAttribute{
+ Description: "Client-side caching policy for the shares.",
+ MarkdownDescription: "Client-side caching policy for the shares.",
+ Optional: true,
+ Computed: true,
+ },
+ "description": schema.StringAttribute{
+ Description: "Description for this SMB share.",
+ MarkdownDescription: "Description for this SMB share.",
+ Optional: true,
+ Computed: true,
+ },
+ "directory_create_mask": schema.Int64Attribute{
+ Description: "Directory create mask bits.",
+ MarkdownDescription: "Directory create mask bits.",
+ Optional: true,
+ Computed: true,
+ },
+ "directory_create_mode": schema.Int64Attribute{
+ Description: "Directory create mode bits.",
+ MarkdownDescription: "Directory create mode bits.",
+ Optional: true,
+ Computed: true,
+ },
+ "file_create_mask": schema.Int64Attribute{
+ Description: "File create mask bits.",
+ MarkdownDescription: "File create mask bits.",
+ Optional: true,
+ Computed: true,
+ },
+ "file_create_mode": schema.Int64Attribute{
+ Description: "File create mode bits.",
+ MarkdownDescription: "File create mode bits.",
+ Optional: true,
+ Computed: true,
+ },
+ "file_filter_extensions": schema.ListAttribute{
+ Description: "Specifies the list of file extensions.",
+ MarkdownDescription: "Specifies the list of file extensions.",
+ Optional: true,
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "file_filter_type": schema.StringAttribute{
+ Description: "Specifies if filter list is for deny or allow. Default is deny.",
+ MarkdownDescription: "Specifies if filter list is for deny or allow. Default is deny.",
+ Optional: true,
+ Computed: true,
+ },
+ "file_filtering_enabled": schema.BoolAttribute{
+ Description: "Enables file filtering on this zone.",
+ MarkdownDescription: "Enables file filtering on this zone.",
+ Optional: true,
+ Computed: true,
+ },
+ "hide_dot_files": schema.BoolAttribute{
+ Description: "Hide files and directories that begin with a period '.'.",
+ MarkdownDescription: "Hide files and directories that begin with a period '.'.",
+ Optional: true,
+ Computed: true,
+ },
+ "host_acl": schema.ListAttribute{
+ Description: "An ACL expressing which hosts are allowed access. A deny clause must be the final entry.",
+ MarkdownDescription: "An ACL expressing which hosts are allowed access. A deny clause must be the final entry.",
+ Optional: true,
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "impersonate_guest": schema.StringAttribute{
+ Description: "Specify the condition in which user access is done as the guest account.",
+ MarkdownDescription: "Specify the condition in which user access is done as the guest account.",
+ Optional: true,
+ Computed: true,
+ },
+ "impersonate_user": schema.StringAttribute{
+ Description: "User account to be used as guest account.",
+ MarkdownDescription: "User account to be used as guest account.",
+ Optional: true,
+ Computed: true,
+ },
+ "inheritable_path_acl": schema.BoolAttribute{
+ Description: "Set the inheritable ACL on the share path.",
+ MarkdownDescription: "Set the inheritable ACL on the share path.",
+ Optional: true,
+ Computed: true,
+ },
+ "mangle_byte_start": schema.Int64Attribute{
+ Description: "Specifies the wchar_t starting point for automatic byte mangling.",
+ MarkdownDescription: "Specifies the wchar_t starting point for automatic byte mangling.",
+ Optional: true,
+ Computed: true,
+ },
+ "mangle_map": schema.ListAttribute{
+ Description: "Character mangle map.",
+ MarkdownDescription: "Character mangle map.",
+ Optional: true,
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "name": schema.StringAttribute{
+ Description: "Share name.",
+ MarkdownDescription: "Share name.",
+ Required: true,
+ },
+ "ntfs_acl_support": schema.BoolAttribute{
+ Description: "Support NTFS ACLs on files and directories.",
+ MarkdownDescription: "Support NTFS ACLs on files and directories.",
+ Optional: true,
+ Computed: true,
+ },
+ "oplocks": schema.BoolAttribute{
+ Description: "Support oplocks.",
+ MarkdownDescription: "Support oplocks.",
+ Optional: true,
+ Computed: true,
+ },
+ "path": schema.StringAttribute{
+ Description: "Path of share within /ifs.",
+ MarkdownDescription: "Path of share within /ifs.",
+ Required: true,
+ },
+ "permissions": schema.ListNestedAttribute{
+ Description: "Specifies an ordered list of permission modifications.",
+ MarkdownDescription: "Specifies an ordered list of permission modifications.",
+ Required: true,
+ PlanModifiers: []planmodifier.List{listplanmodifier.UseStateForUnknown()},
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "permission": schema.StringAttribute{
+ Description: "Specifies the file system rights that are allowed or denied.",
+ MarkdownDescription: "Specifies the file system rights that are allowed or denied.",
+ Required: true,
+ },
+ "permission_type": schema.StringAttribute{
+ Description: "Determines whether the permission is allowed or denied.",
+ MarkdownDescription: "Determines whether the permission is allowed or denied.",
+ Required: true,
+ },
+ "trustee": schema.SingleNestedAttribute{
+ Description: "Specifies the persona of the file group.",
+ MarkdownDescription: "Specifies the persona of the file group.",
+ Required: true,
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.",
+ MarkdownDescription: "Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.",
+ Optional: true,
+ Computed: true,
+ },
+ "name": schema.StringAttribute{
+ Description: "Specifies the persona name, which must be combined with a type.",
+ MarkdownDescription: "Specifies the persona name, which must be combined with a type.",
+ Optional: true,
+ Computed: true,
+ },
+ "type": schema.StringAttribute{
+ Description: "Specifies the type of persona, which must be combined with a name.",
+ MarkdownDescription: "Specifies the type of persona, which must be combined with a name.",
+ Optional: true,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ "run_as_root": schema.ListNestedAttribute{
+ Description: "Allow account to run as root.",
+ MarkdownDescription: "Allow account to run as root.",
+ Optional: true,
+ Computed: true,
+ Default: listdefault.StaticValue(types.ListNull(types.ObjectType{AttrTypes: map[string]attr.Type{
+ "id": types.StringType, "name": types.StringType, "type": types.StringType}})),
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Description: "Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.",
+ MarkdownDescription: "Specifies the serialized form of a persona, which can be 'UID:0', 'USER:name', 'GID:0', 'GROUP:wheel', or 'SID:S-1-1'.",
+ Optional: true,
+ Computed: true,
+ },
+ "name": schema.StringAttribute{
+ Description: "Specifies the persona name, which must be combined with a type.",
+ MarkdownDescription: "Specifies the persona name, which must be combined with a type.",
+ Optional: true,
+ Computed: true,
+ },
+ "type": schema.StringAttribute{
+ Description: "Specifies the type of persona, which must be combined with a name.",
+ MarkdownDescription: "Specifies the type of persona, which must be combined with a name.",
+ Optional: true,
+ Computed: true,
+ },
+ },
+ },
+ },
+ "smb3_encryption_enabled": schema.BoolAttribute{
+ Description: "Enables SMB3 encryption for the share.",
+ MarkdownDescription: "Enables SMB3 encryption for the share.",
+ Optional: true,
+ Computed: true,
+ },
+ "sparse_file": schema.BoolAttribute{
+ Description: "Enables sparse file.",
+ MarkdownDescription: "Enables sparse file.",
+ Optional: true,
+ Computed: true,
+ },
+ "strict_ca_lockout": schema.BoolAttribute{
+ Description: "Specifies if persistent opens would do strict lockout on the share.",
+ MarkdownDescription: "Specifies if persistent opens would do strict lockout on the share.",
+ Optional: true,
+ Computed: true,
+ },
+ "strict_flush": schema.BoolAttribute{
+ Description: "Handle SMB flush operations.",
+ MarkdownDescription: "Handle SMB flush operations.",
+ Optional: true,
+ Computed: true,
+ },
+ "strict_locking": schema.BoolAttribute{
+ Description: "Specifies whether byte range locks contend against SMB I/O.",
+ MarkdownDescription: "Specifies whether byte range locks contend against SMB I/O.",
+ Optional: true,
+ Computed: true,
+ },
+ "zone": schema.StringAttribute{
+ Description: "Name of the access zone to which to move this SMB share.",
+ MarkdownDescription: "Name of the access zone to which to move this SMB share.",
+ Optional: true,
+ },
+ "zid": schema.Int64Attribute{
+ Description: "Numeric ID of the access zone which contains this SMB share.",
+ MarkdownDescription: "Numeric ID of the access zone which contains this SMB share.",
+ Optional: true,
+ Computed: true,
+ },
+ },
+ }
+}
+
+// Configure - defines configuration for smb share resource.
+func (r *SmbShareResource) Configure(ctx context.Context, req resource.ConfigureRequest, res *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+ c, ok := req.ProviderData.(*client.Client)
+ if !ok {
+ res.Diagnostics.AddError(
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *c.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ )
+ return
+ }
+ r.client = c
+}
+
+// Create allocates the resource.
+func (r SmbShareResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
+ tflog.Info(ctx, "creating smb share")
+
+ var sharePlan models.SmbShareResource
+ diags := request.Plan.Get(ctx, &sharePlan)
+ //cachedPermission := sharePlan.Permissions
+
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ shareToCreate := powerscale.V7SmbShare{}
+ // Get param from tf input
+ err := helper.ReadFromState(ctx, sharePlan, &shareToCreate)
+ if err != nil {
+ response.Diagnostics.AddError("Error creating smb share",
+ fmt.Sprintf("Could not read smb share param of Path: %s with error: %s", sharePlan.Path.ValueString(), err.Error()),
+ )
+ return
+ }
+ shareID, err := helper.CreateSmbShare(ctx, r.client, shareToCreate)
+ if err != nil {
+ response.Diagnostics.AddError("Error creating smb share",
+ fmt.Sprintf("Could not create smb share of Path: %s with error: %s", sharePlan.Path.ValueString(), err.Error()),
+ )
+ return
+ }
+ tflog.Debug(ctx, fmt.Sprintf("smb share %s created", shareID.Id), map[string]interface{}{
+ "smbShareResponse": shareID,
+ })
+
+ getShareResponse, err := helper.GetSmbShare(ctx, r.client, shareID.Id)
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error creating smb share",
+ fmt.Sprintf("Could not read smb share %s with error: %s", shareID, err.Error()),
+ )
+ return
+ }
+
+ // update resource state according to response
+ if len(getShareResponse.Shares) <= 0 {
+ response.Diagnostics.AddError(
+ "Error creating smb share",
+ fmt.Sprintf("Could not get created smb share state %s with error: smb share not found", shareID),
+ )
+ return
+ }
+ createdShare := getShareResponse.Shares[0]
+ err = helper.CopyFieldsToNonNestedModel(ctx, createdShare, &sharePlan)
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error creating smb share",
+ fmt.Sprintf("Could not read smb share %s with error: %s", shareID, err.Error()),
+ )
+ return
+ }
+
+ diags = response.State.Set(ctx, sharePlan)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "create smb share completed")
+}
+
+// Read reads the resource state.
+func (r SmbShareResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
+ tflog.Info(ctx, "reading smb share")
+ var shareState models.SmbShareResource
+ diags := request.State.Get(ctx, &shareState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ shareID := shareState.ID
+ tflog.Debug(ctx, "calling get smb share by ID", map[string]interface{}{
+ "smbShareID": shareID,
+ })
+ shareResponse, err := helper.GetSmbShare(ctx, r.client, shareID.ValueString())
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error reading smb share",
+ fmt.Sprintf("Could not read smb share %s with error: %s", shareID, err.Error()),
+ )
+ return
+ }
+
+ if len(shareResponse.Shares) <= 0 {
+ response.Diagnostics.AddError(
+ "Error reading smb share",
+ fmt.Sprintf("Could not read smb share %s from pscale with error: smb share not found", shareID),
+ )
+ return
+ }
+ tflog.Debug(ctx, "updating read smb share state", map[string]interface{}{
+ "smbShareResponse": shareResponse,
+ "smbShareState": shareState,
+ })
+ err = helper.CopyFieldsToNonNestedModel(ctx, shareResponse.Shares[0], &shareState)
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error read smb share",
+ fmt.Sprintf("Could not read smb share struct %s with error: %s", shareID, err.Error()),
+ )
+ return
+ }
+ diags = response.State.Set(ctx, shareState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "read smb share completed")
+}
+
+// Update updates the resource state
+func (r SmbShareResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
+ tflog.Info(ctx, "updating smb share")
+ var sharePlan models.SmbShareResource
+ diags := request.Plan.Get(ctx, &sharePlan)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ var shareState models.SmbShareResource
+ diags = response.State.Get(ctx, &shareState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ tflog.Debug(ctx, "calling update smb share", map[string]interface{}{
+ "sharePlan": sharePlan,
+ "shareState": shareState,
+ })
+
+ shareID := shareState.ID.ValueString()
+ var shareToUpdate powerscale.V7SmbShareExtendedExtended
+ // Get param from tf input
+ err := helper.ReadFromState(ctx, sharePlan, &shareToUpdate)
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error update smb share",
+ fmt.Sprintf("Could not read smb share struct %s with error: %s", shareID, err.Error()),
+ )
+ return
+ }
+ err = helper.UpdateSmbShare(ctx, r.client, shareID, shareToUpdate)
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error updating smb share",
+ fmt.Sprintf("Could not update smb share %s with error: %s", shareID, err.Error()),
+ )
+ return
+ }
+
+ tflog.Debug(ctx, "calling get smb share by ID on pscale client", map[string]interface{}{
+ "smbShareID": shareID,
+ })
+ updatedShare, err := helper.GetSmbShare(ctx, r.client, shareID)
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error updating smb share",
+ fmt.Sprintf("Could not read smb share %s with error: %s", shareID, err.Error()),
+ )
+ return
+ }
+
+ if len(updatedShare.Shares) <= 0 {
+ response.Diagnostics.AddError(
+ "Error reading smb share",
+ fmt.Sprintf("Could not read smb share %s from pscale with error: smb share not found", shareID),
+ )
+ return
+ }
+
+ err = helper.CopyFieldsToNonNestedModel(ctx, updatedShare.Shares[0], &shareState)
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error read smb share",
+ fmt.Sprintf("Could not read smb share struct %s with error: %s", shareID, err.Error()),
+ )
+ return
+ }
+ // Zone need to be manually set
+ shareState.Zone = sharePlan.Zone
+ diags = response.State.Set(ctx, shareState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+ tflog.Info(ctx, "update smb share completed")
+}
+
+// Delete deletes the resource.
+func (r SmbShareResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
+ tflog.Info(ctx, "deleting smb share")
+ var shareState models.SmbShareResource
+ diags := request.State.Get(ctx, &shareState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ shareID := shareState.ID.ValueString()
+ if diags.HasError() {
+ response.Diagnostics.Append(diags...)
+ }
+
+ tflog.Debug(ctx, "calling delete smb share on pscale client", map[string]interface{}{
+ "smbShareID": shareID,
+ })
+ err := helper.DeleteSmbShare(ctx, r.client, shareID)
+ if err != nil {
+ response.Diagnostics.AddError(
+ "Error deleting smb share",
+ fmt.Sprintf("Could not remove smb share ID: %s with error: %s ",
+ shareID, err.Error()),
+ )
+ }
+ response.State.RemoveResource(ctx)
+ tflog.Info(ctx, "delete smb share completed")
+}
+
+// ImportState imports the resource state.
+func (r SmbShareResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response)
+}
diff --git a/powerscale/provider/smb_share_resource_test.go b/powerscale/provider/smb_share_resource_test.go
new file mode 100644
index 00000000..e7177e6f
--- /dev/null
+++ b/powerscale/provider/smb_share_resource_test.go
@@ -0,0 +1,288 @@
+/*
+Copyright (c) 2023 Dell Inc., or its subsidiaries. All Rights Reserved.
+
+Licensed under the Mozilla Public License Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://mozilla.org/MPL/2.0/
+
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package provider
+
+import (
+ "context"
+ powerscale "dell/powerscale-go-client"
+ "fmt"
+ . "github.com/bytedance/mockey"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "github.com/stretchr/testify/assert"
+ "regexp"
+ "terraform-provider-powerscale/client"
+ "terraform-provider-powerscale/powerscale/helper"
+ "testing"
+)
+
+func TestAccSmbShareResource(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ Config: ProviderConfig + SmbShareResourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "name", shareName),
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "ca_timeout", "120"),
+ ),
+ },
+ // ImportState testing
+ {
+ ResourceName: "powerscale_smb_share.share_test",
+ ImportState: true,
+ ImportStateCheck: func(states []*terraform.InstanceState) error {
+ assert.Equal(t, shareName, states[0].Attributes["id"])
+ assert.Equal(t, "120", states[0].Attributes["ca_timeout"])
+ return nil
+ },
+ },
+ // Update
+ {
+ Config: ProviderConfig + SmbShareUpdatedResourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "allow_delete_readonly", "true"),
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "ca_timeout", "30"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccSmbShareResourceErrorRead(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ Config: ProviderConfig + SmbShareResourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "name", shareName),
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "ca_timeout", "120"),
+ ),
+ },
+ // ImportState testing get none share
+ {
+ ResourceName: "powerscale_smb_share.share_test",
+ ImportState: true,
+ PreConfig: func() {
+ FunctionMocker = Mock(helper.GetSmbShare).Return(&powerscale.V7SmbSharesExtended{}, nil).Build()
+ },
+ ExpectError: regexp.MustCompile(".not found"),
+ },
+ // ImportState testing get error
+ {
+ ResourceName: "powerscale_smb_share.share_test",
+ ImportState: true,
+ PreConfig: func() {
+ FunctionMocker.Release()
+ FunctionMocker = Mock(helper.GetSmbShare).Return(nil, fmt.Errorf("mock error")).Build()
+ },
+ ExpectError: regexp.MustCompile("mock error"),
+ },
+ },
+ })
+}
+
+func TestAccSmbShareResourceErrorUpdate(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ Config: ProviderConfig + SmbShareResourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "name", shareName),
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "ca_timeout", "120"),
+ ),
+ },
+ // Update get error
+ {
+ Config: ProviderConfig + SmbShareUpdatedResourceConfig,
+ PreConfig: func() {
+ FunctionMocker = Mock(helper.UpdateSmbShare).Return(fmt.Errorf("mock error")).Build()
+ },
+ ExpectError: regexp.MustCompile("mock error"),
+ },
+ // Update get none share
+ {
+ PreConfig: func() {
+ FunctionMocker.Release()
+ FunctionMocker = Mock(helper.GetSmbShare).Return(&powerscale.V7SmbSharesExtended{}, nil).Build().
+ When(func(ctx context.Context, client *client.Client, shareID string) bool {
+ return FunctionMocker.Times() == 2
+ })
+ },
+ Config: ProviderConfig + SmbShareUpdatedResourceConfig,
+ ExpectError: regexp.MustCompile(".not found"),
+ },
+ // Update get error
+ {
+ Config: ProviderConfig + SmbShareUpdatedResourceConfig,
+ PreConfig: func() {
+ FunctionMocker.Release()
+ FunctionMocker = Mock(helper.GetSmbShare).Return(nil, fmt.Errorf("mock error")).Build().
+ When(func(ctx context.Context, client *client.Client, shareID string) bool {
+ return FunctionMocker.Times() == 2
+ })
+ },
+ ExpectError: regexp.MustCompile("mock error"),
+ },
+ //Update Invalid Config
+ {
+ PreConfig: func() {
+ FunctionMocker.Release()
+ },
+ Config: ProviderConfig + SmbShareInvalidResourceConfig,
+ ExpectError: regexp.MustCompile(".*Bad Request*."),
+ },
+ },
+ })
+}
+
+func TestAccSmbShareResourceErrorCreate(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: ProviderConfig + SmbShareInvalidResourceConfig,
+ ExpectError: regexp.MustCompile(".*Bad Request*."),
+ },
+ },
+ })
+}
+
+func TestAccSmbShareResourceErrorCopyField(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ Config: ProviderConfig + SmbShareResourceConfig,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "name", shareName),
+ resource.TestCheckResourceAttr("powerscale_smb_share.share_test", "ca_timeout", "120"),
+ ),
+ },
+ {
+ ResourceName: "powerscale_smb_share.share_test",
+ ImportState: true,
+ PreConfig: func() {
+ FunctionMocker = Mock(helper.CopyFieldsToNonNestedModel).Return(fmt.Errorf("mock error")).Build()
+ },
+ Config: ProviderConfig + SmbShareResourceConfig,
+ ExpectError: regexp.MustCompile("mock error"),
+ },
+ {
+ PreConfig: func() {
+ FunctionMocker.Release()
+ FunctionMocker = Mock(helper.CopyFieldsToNonNestedModel).Return(fmt.Errorf("mock error")).Build()
+ },
+ Config: ProviderConfig + SmbShareUpdatedResourceConfig,
+ ExpectError: regexp.MustCompile("mock error"),
+ },
+ },
+ })
+}
+
+func TestAccSmbShareResourceErrorReadState(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ Steps: []resource.TestStep{
+ // Create and Read testing
+ {
+ PreConfig: func() {
+ FunctionMocker = Mock(helper.ReadFromState).Return(fmt.Errorf("mock error")).Build()
+ },
+ Config: ProviderConfig + SmbShareResourceConfig,
+ ExpectError: regexp.MustCompile("mock error"),
+ },
+ },
+ })
+}
+
+var shareName = "tfacc_test_smb_share"
+
+var SmbShareResourceConfig = fmt.Sprintf(`
+resource "powerscale_smb_share" "share_test" {
+ auto_create_directory = true
+ name = "%s"
+ path = "/ifs/%s"
+ permissions = [
+ {
+ permission = "full"
+ permission_type = "allow"
+ trustee = {
+ id = "SID:S-1-1-0",
+ name = "Everyone",
+ type = "wellknown"
+ }
+ }
+ ]
+ zone = "System"
+}
+`, shareName, shareName)
+
+var SmbShareInvalidResourceConfig = fmt.Sprintf(`
+resource "powerscale_smb_share" "share_test" {
+ auto_create_directory = true
+ name = "%s"
+ path = "/ifs/%s"
+ zone = "System"
+ permissions = [
+ {
+ permission = "full"
+ permission_type = "allow"
+ trustee = {
+ id = "SID:S-1-1-0",
+ name = "invalid",
+ type = "invalid"
+ }
+ }
+ ]
+}
+`, shareName, shareName)
+
+var SmbShareUpdatedResourceConfig = fmt.Sprintf(`
+resource "powerscale_smb_share" "share_test" {
+ auto_create_directory = true
+ name = "%s"
+ path = "/ifs/%s"
+ permissions = [
+ {
+ permission = "full"
+ permission_type = "allow"
+ trustee = {
+ id = "SID:S-1-1-0",
+ name = "Everyone",
+ type = "wellknown"
+ }
+ }
+ ]
+ allow_delete_readonly = true
+ ca_timeout = 30
+ zone = "System"
+}
+`, shareName, shareName)