From 749b07150ec4bfffc20beae666b727c0399a9ae3 Mon Sep 17 00:00:00 2001 From: Sachin Saxena <103450525+sachinsaxena-okta@users.noreply.github.com> Date: Tue, 30 Aug 2022 11:55:17 -0500 Subject: [PATCH] Ssaxena/import terraform id fix (#69) * ProjectGroup resource Terraform Id changes * Populated ProjectGroup Id as ASA Project Group UUID * Added comment * Added ASA resource uuid for group & server enrollment token * nit * Fixed test * Delete darwin_amd64 * Added Documentation for import and fixed import error messages * Fixed tests and changed pattern for user resource too * Bring Id pattern changes for project and AD Task Settings from PR #65 Id Pattern changes were spread across 2 PRs. I think it's better to have one PR for these changes as it's related and will help in versioning too. It will also unblock other PRs. * Docs check fix * Fixed id pattern divider * doc update check --- Makefile | 2 +- docs/data-sources/project.md | 1 - docs/resources/ad_connection.md | 7 ++ docs/resources/ad_task_settings.md | 7 ++ docs/resources/gateway_setup_token.md | 7 ++ docs/resources/group.md | 7 ++ docs/resources/kubernetes_cluster.md | 7 ++ .../kubernetes_cluster_connection.md | 7 ++ docs/resources/kubernetes_cluster_group.md | 7 ++ docs/resources/project.md | 8 ++- docs/resources/project_group.md | 7 ++ docs/resources/server_enrollment_token.md | 7 ++ docs/resources/user.md | 7 ++ .../ad_connection_example.tf | 2 +- .../resources/oktapam_ad_connection/import.sh | 2 + .../oktapam_ad_task_settings/import.sh | 2 + .../oktapam_gateway_setup_token/import.sh | 2 + examples/resources/oktapam_group/import.sh | 2 + .../oktapam_kubernetes_cluster/import.sh | 2 + .../import.sh | 2 + .../import.sh | 2 + examples/resources/oktapam_project/import.sh | 2 + .../resources/oktapam_project_group/import.sh | 2 + .../oktapam_server_enrollment_token/import.sh | 2 + examples/resources/oktapam_user/import.sh | 2 + oktapam/client/project.go | 4 +- oktapam/client/project_group.go | 5 ++ oktapam/client/user.go | 4 ++ oktapam/data_source_group.go | 6 +- oktapam/data_source_project.go | 9 +-- oktapam/data_source_project_group.go | 20 ++---- .../data_source_server_enrollment_token.go | 10 ++- oktapam/resource_ad_task_settings.go | 56 +++++++++------- oktapam/resource_ad_task_settings_test.go | 35 ++++++---- oktapam/resource_group.go | 30 ++++++--- oktapam/resource_group_test.go | 14 +++- oktapam/resource_project.go | 21 +++--- oktapam/resource_project_group.go | 66 +++++++++++-------- oktapam/resource_project_group_test.go | 26 ++++++-- oktapam/resource_project_test.go | 14 +++- oktapam/resource_server_enrollment_token.go | 56 +++++++++------- .../resource_server_enrollment_token_test.go | 22 +++++-- oktapam/resource_user.go | 40 ++++++++--- oktapam/resource_user_test.go | 22 +++++-- 44 files changed, 389 insertions(+), 176 deletions(-) create mode 100644 examples/resources/oktapam_ad_connection/import.sh create mode 100644 examples/resources/oktapam_ad_task_settings/import.sh create mode 100644 examples/resources/oktapam_gateway_setup_token/import.sh create mode 100644 examples/resources/oktapam_group/import.sh create mode 100644 examples/resources/oktapam_kubernetes_cluster/import.sh create mode 100644 examples/resources/oktapam_kubernetes_cluster_connection/import.sh create mode 100644 examples/resources/oktapam_kubernetes_cluster_group/import.sh create mode 100644 examples/resources/oktapam_project/import.sh create mode 100644 examples/resources/oktapam_project_group/import.sh create mode 100644 examples/resources/oktapam_server_enrollment_token/import.sh create mode 100644 examples/resources/oktapam_user/import.sh diff --git a/Makefile b/Makefile index 29da5578c0..d99065c1c5 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ NAMESPACE=pam NAME=oktapam BINARY=terraform-provider-${NAME} # On verion changes, update tag-checks.yml -VERSION=0.2.2 +VERSION=0.3.0 OS_ARCH=$(shell go env GOOS)_$(shell go env GOARCH) PLUGIN_DIR=~/.terraform.d/plugins DOCGEN_RESOURCES_DIR=docgen-resources diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index c42a2f6b58..f3f3f0e116 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -28,7 +28,6 @@ Returns a previously created ASA Project. For details, [Projects](https://help.o - `id` (String) The ID of this resource. - `next_unix_gid` (Number) The GID to use when creating a new ASA Server User. Default value starts at 63001. - `next_unix_uid` (Number) The UID to use when creating a new ASA Server User. Default value starts at 60001. -- `project_id` (String) UUID of ASA Project. - `rdp_session_recording` (Boolean) If `true`, enable remote desktop protocol (RDP) recording on all servers in the ASA Project. - `require_preauth_for_creds` (Boolean) If `true`, require preauthorization before an ASA User can retrieve credentials to sign in. - `ssh_certificate_type` (String) The SSH certificate type used by access requests. Options include: [`CERT_TYPE_ED25519_01`, `CERT_TYPE_ECDSA_521_01`, `CERT_TYPE_ECDSA_384_01`, `CERT_TYPE_ECDSA_256_01`, `CERT_TYPE_RSA_01`]. 'CERT_TYPE_RSA_01' is a deprecated key algorithm type. This option should only be used to connect to legacy systems that cannot use newer SSH versions. If you do need to use 'CERT_TYPE_RSA_01', it is recommended to connect via a gateway with traffic forwarding. Otherwise, please use a more current key algorithm. If left unspecified, 'CERT_TYPE_ED25519_01' is used by default. diff --git a/docs/resources/ad_connection.md b/docs/resources/ad_connection.md index 2603dc3c65..44d5aca7e1 100644 --- a/docs/resources/ad_connection.md +++ b/docs/resources/ad_connection.md @@ -34,4 +34,11 @@ An Active Directory (AD) Connection to query AD Domain for available servers. Fo - `deleted_at` (String) The UTC time of resource deletion. Format is '2022-01-01 00:00:00 +0000 UTC'. - `id` (String) The ID of this resource. +## Import +Import is supported using the following syntax: + +```shell +# Gateway Setup Token can be imported using ID of this resource, e.g., +terraform import oktapam_ad_connection.example {{id}} +``` diff --git a/docs/resources/ad_task_settings.md b/docs/resources/ad_task_settings.md index 17f4fd5da9..9e1f4252a8 100644 --- a/docs/resources/ad_task_settings.md +++ b/docs/resources/ad_task_settings.md @@ -65,4 +65,11 @@ Optional: - `is_guid` (Boolean) Identifies an AD attribute as a Globally Unique Identifier (GUID) +## Import +Import is supported using the following syntax: + +```shell +# AD Task Settings can be imported using AD Connection ID and ID of this resource separated by a forward slash (/), e.g., +terraform import oktapam_ad_task_settings.example {{connection_id}}/{{id}} +``` diff --git a/docs/resources/gateway_setup_token.md b/docs/resources/gateway_setup_token.md index ef029401d3..93fc00ba87 100644 --- a/docs/resources/gateway_setup_token.md +++ b/docs/resources/gateway_setup_token.md @@ -26,4 +26,11 @@ A token for ASA Gateway enrollment. For details, see [Configure an Advanced Serv - `id` (String) The ID of this resource. - `token` (String) The secret used for resource enrollment. +## Import +Import is supported using the following syntax: + +```shell +# Gateway Setup Token can be imported using ID of this resource, e.g., +terraform import oktapam_gateway_setup_token.example {{id}} +``` diff --git a/docs/resources/group.md b/docs/resources/group.md index efa39ba8ea..7a16ba6089 100644 --- a/docs/resources/group.md +++ b/docs/resources/group.md @@ -28,4 +28,11 @@ A set of ASA Users. For details, see [Groups](https://help.okta.com/asa/en-us/Co - `deleted_at` (String) The UTC time of resource deletion. Format is '2022-01-01 00:00:00 +0000 UTC'. - `id` (String) The ID of this resource. +## Import +Import is supported using the following syntax: + +```shell +# Group can be imported using Group Name, e.g., +terraform import oktapam_group.example {{name}} +``` diff --git a/docs/resources/kubernetes_cluster.md b/docs/resources/kubernetes_cluster.md index 6e1d284fed..e483c36b41 100644 --- a/docs/resources/kubernetes_cluster.md +++ b/docs/resources/kubernetes_cluster.md @@ -29,4 +29,11 @@ Beta Feature: Represents a Kubernetes cluster that has been registered with ASA. - `id` (String) The ID of this resource. - `oidc_issuer_url` (String) The OIDC Issuer URL to use when configuring your Kubernetes cluster. +## Import +Import is supported using the following syntax: + +```shell +# Kubernetes Cluster can be imported using ID of this resource, e.g., +terraform import oktapam_kubernetes_cluster.example {{id}} +``` diff --git a/docs/resources/kubernetes_cluster_connection.md b/docs/resources/kubernetes_cluster_connection.md index 3b5e46490b..dd7f8844c7 100644 --- a/docs/resources/kubernetes_cluster_connection.md +++ b/docs/resources/kubernetes_cluster_connection.md @@ -28,4 +28,11 @@ Beta Feature: A set of details describing how to connect to an existing Kubernet - `id` (String) The ID of this resource. +## Import +Import is supported using the following syntax: + +```shell +# Kubernetes Cluster Connection can be imported using ID of this resource, e.g., +terraform import oktapam_kubernetes_cluster_connection.example {{id}} +``` diff --git a/docs/resources/kubernetes_cluster_group.md b/docs/resources/kubernetes_cluster_group.md index 49778653cc..0a35c29053 100644 --- a/docs/resources/kubernetes_cluster_group.md +++ b/docs/resources/kubernetes_cluster_group.md @@ -28,4 +28,11 @@ Beta Feature: A mapping of Kubernetes cluster to an ASA Group. Members of the pr - `id` (String) The ID of this resource. +## Import +Import is supported using the following syntax: + +```shell +# Kubernetes Cluster Group can be imported using ID of this resource, e.g., +terraform import oktapam_kubernetes_cluster_group.example {{id}} +``` diff --git a/docs/resources/project.md b/docs/resources/project.md index 44083d3d36..858188255c 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -35,7 +35,13 @@ An ASA construct that contains servers and is used to grant end users access to - `deleted_at` (String) The UTC time of resource deletion. Format is '2022-01-01 00:00:00 +0000 UTC'. - `id` (String) The ID of this resource. -- `project_id` (String) UUID of ASA Project. - `team` (String) The human-readable name of the ASA Team that owns the resource. Values are lower-case. +## Import +Import is supported using the following syntax: + +```shell +# Project can be imported using Project Name, e.g., +terraform import oktapam_project.example {{name}} +``` diff --git a/docs/resources/project_group.md b/docs/resources/project_group.md index 0526fa1282..d54d0b2533 100644 --- a/docs/resources/project_group.md +++ b/docs/resources/project_group.md @@ -33,4 +33,11 @@ Assigns an ASA Group to a Project and configures how that group is created on se - `id` (String) The ID of this resource. - `removed_at` (String) UTC time of resource removal from parent resource. Format is '2022-01-01 00:00:00 +0000 UTC'. +## Import +Import is supported using the following syntax: + +```shell +# Project Group can be imported using Project Name and Group Name separated by a forward slash (/), e.g., +terraform import oktapam_project_group.example {{project_name}}/{{group_name}} +``` diff --git a/docs/resources/server_enrollment_token.md b/docs/resources/server_enrollment_token.md index 11fb92758a..80246971fb 100644 --- a/docs/resources/server_enrollment_token.md +++ b/docs/resources/server_enrollment_token.md @@ -27,4 +27,11 @@ A token used to enroll servers in an ASA Project. For details, see [Enroll a ser - `issued_at` (String) The UTC time when the token was issued. Format is '2022-01-01 00:00:00 +0000 UTC'. - `token` (String) The secret used for resource enrollment. +## Import +Import is supported using the following syntax: + +```shell +# Server Enrollment Token can be imported using Project Name and ID of the resource separated by a forward slash (/), e.g., +terraform import oktapam_server_enrollment_token.example {{project_name}}/{{id}} +``` diff --git a/docs/resources/user.md b/docs/resources/user.md index ddc031b4c3..ea0e1f82e9 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -31,4 +31,11 @@ An ASA User. Valid user types are `human` and `service`. For more information ch - `server_user_name` (String) The name of the corresponding ASA Server User. - `team_name` (String) The human-readable name of the ASA Team that owns the resource. Values are lower-case. +## Import +Import is supported using the following syntax: + +```shell +# User can be imported using User Name and User Type of the resource separated by a forward slash (/), e.g., +terraform import oktapam_user.example {{name}}/{{type}} +``` diff --git a/examples/oktapam_ad_connection/ad_connection_example.tf b/examples/oktapam_ad_connection/ad_connection_example.tf index 55f9770356..a7cd511a66 100644 --- a/examples/oktapam_ad_connection/ad_connection_example.tf +++ b/examples/oktapam_ad_connection/ad_connection_example.tf @@ -41,7 +41,7 @@ resource "oktapam_ad_task_settings" "test_ad_task_settings" { rule_assignments { base_dn = "ou=real,dc=dev-test,dc=sudo,dc=wtf" ldap_query_filter = "(objectclass=computer)" - project_id = oktapam_project.test_project.project_id + project_id = oktapam_project.test_project.id priority = 1 } } diff --git a/examples/resources/oktapam_ad_connection/import.sh b/examples/resources/oktapam_ad_connection/import.sh new file mode 100644 index 0000000000..e34e9dae9b --- /dev/null +++ b/examples/resources/oktapam_ad_connection/import.sh @@ -0,0 +1,2 @@ +# Gateway Setup Token can be imported using ID of this resource, e.g., +terraform import oktapam_ad_connection.example {{id}} \ No newline at end of file diff --git a/examples/resources/oktapam_ad_task_settings/import.sh b/examples/resources/oktapam_ad_task_settings/import.sh new file mode 100644 index 0000000000..b83dccfcde --- /dev/null +++ b/examples/resources/oktapam_ad_task_settings/import.sh @@ -0,0 +1,2 @@ +# AD Task Settings can be imported using AD Connection ID and ID of this resource separated by a forward slash (/), e.g., +terraform import oktapam_ad_task_settings.example {{connection_id}}/{{id}} \ No newline at end of file diff --git a/examples/resources/oktapam_gateway_setup_token/import.sh b/examples/resources/oktapam_gateway_setup_token/import.sh new file mode 100644 index 0000000000..73b7f9eef2 --- /dev/null +++ b/examples/resources/oktapam_gateway_setup_token/import.sh @@ -0,0 +1,2 @@ +# Gateway Setup Token can be imported using ID of this resource, e.g., +terraform import oktapam_gateway_setup_token.example {{id}} \ No newline at end of file diff --git a/examples/resources/oktapam_group/import.sh b/examples/resources/oktapam_group/import.sh new file mode 100644 index 0000000000..24bd32baeb --- /dev/null +++ b/examples/resources/oktapam_group/import.sh @@ -0,0 +1,2 @@ +# Group can be imported using Group Name, e.g., +terraform import oktapam_group.example {{name}} \ No newline at end of file diff --git a/examples/resources/oktapam_kubernetes_cluster/import.sh b/examples/resources/oktapam_kubernetes_cluster/import.sh new file mode 100644 index 0000000000..21291a59c1 --- /dev/null +++ b/examples/resources/oktapam_kubernetes_cluster/import.sh @@ -0,0 +1,2 @@ +# Kubernetes Cluster can be imported using ID of this resource, e.g., +terraform import oktapam_kubernetes_cluster.example {{id}} \ No newline at end of file diff --git a/examples/resources/oktapam_kubernetes_cluster_connection/import.sh b/examples/resources/oktapam_kubernetes_cluster_connection/import.sh new file mode 100644 index 0000000000..34facbf328 --- /dev/null +++ b/examples/resources/oktapam_kubernetes_cluster_connection/import.sh @@ -0,0 +1,2 @@ +# Kubernetes Cluster Connection can be imported using ID of this resource, e.g., +terraform import oktapam_kubernetes_cluster_connection.example {{id}} \ No newline at end of file diff --git a/examples/resources/oktapam_kubernetes_cluster_group/import.sh b/examples/resources/oktapam_kubernetes_cluster_group/import.sh new file mode 100644 index 0000000000..320a5365e5 --- /dev/null +++ b/examples/resources/oktapam_kubernetes_cluster_group/import.sh @@ -0,0 +1,2 @@ +# Kubernetes Cluster Group can be imported using ID of this resource, e.g., +terraform import oktapam_kubernetes_cluster_group.example {{id}} \ No newline at end of file diff --git a/examples/resources/oktapam_project/import.sh b/examples/resources/oktapam_project/import.sh new file mode 100644 index 0000000000..484e067ed4 --- /dev/null +++ b/examples/resources/oktapam_project/import.sh @@ -0,0 +1,2 @@ +# Project can be imported using Project Name, e.g., +terraform import oktapam_project.example {{name}} \ No newline at end of file diff --git a/examples/resources/oktapam_project_group/import.sh b/examples/resources/oktapam_project_group/import.sh new file mode 100644 index 0000000000..42c505a3d3 --- /dev/null +++ b/examples/resources/oktapam_project_group/import.sh @@ -0,0 +1,2 @@ +# Project Group can be imported using Project Name and Group Name separated by a forward slash (/), e.g., +terraform import oktapam_project_group.example {{project_name}}/{{group_name}} \ No newline at end of file diff --git a/examples/resources/oktapam_server_enrollment_token/import.sh b/examples/resources/oktapam_server_enrollment_token/import.sh new file mode 100644 index 0000000000..21896545a2 --- /dev/null +++ b/examples/resources/oktapam_server_enrollment_token/import.sh @@ -0,0 +1,2 @@ +# Server Enrollment Token can be imported using Project Name and ID of the resource separated by a forward slash (/), e.g., +terraform import oktapam_server_enrollment_token.example {{project_name}}/{{id}} \ No newline at end of file diff --git a/examples/resources/oktapam_user/import.sh b/examples/resources/oktapam_user/import.sh new file mode 100644 index 0000000000..06c91f511f --- /dev/null +++ b/examples/resources/oktapam_user/import.sh @@ -0,0 +1,2 @@ +# User can be imported using User Name and User Type of the resource separated by a forward slash (/), e.g., +terraform import oktapam_user.example {{name}}/{{type}} \ No newline at end of file diff --git a/oktapam/client/project.go b/oktapam/client/project.go index 65a5026d22..f81ffe1165 100644 --- a/oktapam/client/project.go +++ b/oktapam/client/project.go @@ -37,7 +37,7 @@ func (p Project) ToResourceMap() map[string]interface{} { m[attributes.Name] = *p.Name } if p.ID != nil { - m[attributes.ProjectID] = *p.ID + m[attributes.ID] = *p.ID } if p.Team != nil { m[attributes.Team] = *p.Team @@ -200,4 +200,4 @@ func (c OktaPAMClient) DeleteProject(ctx context.Context, projectName string) er _, err = checkStatusCode(resp, http.StatusNoContent, http.StatusNotFound) return err -} +} \ No newline at end of file diff --git a/oktapam/client/project_group.go b/oktapam/client/project_group.go index de2d9d022c..81f0ed4c56 100644 --- a/oktapam/client/project_group.go +++ b/oktapam/client/project_group.go @@ -16,6 +16,7 @@ import ( ) type ProjectGroup struct { + ID *string `json:"id"` Project *string `json:"_"` Group *string `json:"group"` DeletedAt *string `json:"deleted_at,omitempty"` @@ -176,6 +177,10 @@ func (p ListProjectGroupsParameters) toQueryParametersMap() map[string]string { return m } +func (pg ProjectGroup) Exists() bool { + return utils.IsNonEmpty(pg.ID) +} + type ProjectGroupsListResponse struct { ProjectGroups []ProjectGroup `json:"list"` } diff --git a/oktapam/client/user.go b/oktapam/client/user.go index a9cdbf8fa9..a0dd299478 100644 --- a/oktapam/client/user.go +++ b/oktapam/client/user.go @@ -17,6 +17,7 @@ import ( ) type User struct { + ID *string `json:"id"` Name *string `json:"name"` TeamName *string `json:"team_name"` ServerUserName *string `json:"server_user_name,omitempty"` @@ -51,6 +52,9 @@ func UserFromMap(m map[string]interface{}) (*User, error) { func (su User) ToResourceMap() map[string]interface{} { m := make(map[string]interface{}, 2) + if su.ID != nil { + m[attributes.ID] = *su.ID + } if su.Name != nil { m[attributes.Name] = *su.Name } diff --git a/oktapam/data_source_group.go b/oktapam/data_source_group.go index f524966902..130a63f07e 100644 --- a/oktapam/data_source_group.go +++ b/oktapam/data_source_group.go @@ -57,9 +57,11 @@ func dataSourceGroupFetch(ctx context.Context, d *schema.ResourceData, m interfa } if group != nil { - d.SetId(*group.Name) + d.SetId(*group.ID) for key, value := range group.ToResourceMap() { - d.Set(key, value) + if err := d.Set(key, value); err != nil { + return diag.FromErr(err) + } } } else { logging.Infof("group %s does not exist", name) diff --git a/oktapam/data_source_project.go b/oktapam/data_source_project.go index 6b3d967118..e6528fb16d 100644 --- a/oktapam/data_source_project.go +++ b/oktapam/data_source_project.go @@ -27,11 +27,6 @@ func dataSourceProject() *schema.Resource { Computed: true, // Description is autogenerated }, - attributes.ProjectID: { - Type: schema.TypeString, - Computed: true, - Description: descriptions.ProjectID, - }, attributes.Team: { Type: schema.TypeString, Computed: true, @@ -104,7 +99,7 @@ func dataSourceProjectFetch(ctx context.Context, d *schema.ResourceData, m inter } if project != nil { - d.SetId(*project.Name) + d.SetId(*project.ID) for key, value := range project.ToResourceMap() { d.Set(key, value) } @@ -112,4 +107,4 @@ func dataSourceProjectFetch(ctx context.Context, d *schema.ResourceData, m inter logging.Infof("project %s does not exist", name) } return nil -} +} \ No newline at end of file diff --git a/oktapam/data_source_project_group.go b/oktapam/data_source_project_group.go index 8b0e107f32..f73d3127b8 100644 --- a/oktapam/data_source_project_group.go +++ b/oktapam/data_source_project_group.go @@ -2,7 +2,6 @@ package oktapam import ( "context" - "github.com/okta/terraform-provider-oktapam/oktapam/constants/attributes" "github.com/okta/terraform-provider-oktapam/oktapam/constants/descriptions" "github.com/okta/terraform-provider-oktapam/oktapam/logging" @@ -67,30 +66,25 @@ func dataSourceProjectGroup() *schema.Resource { func dataSourceProjectGroupFetch(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - group := d.Get(attributes.GroupName).(string) - if group == "" { - return diag.Errorf("%s cannot be blank", attributes.GroupName) - } - - project := d.Get(attributes.ProjectName).(string) - if project == "" { - return diag.Errorf("%s cannot be blank", attributes.ProjectName) - } + projectName := d.Get(attributes.ProjectName).(string) + groupName := d.Get(attributes.GroupName).(string) - projectGroup, err := c.GetProjectGroup(ctx, project, group) + projectGroup, err := c.GetProjectGroup(ctx, projectName, groupName) if err != nil { return diag.FromErr(err) } if projectGroup != nil { - d.SetId(createProjectGroupResourceID(*projectGroup.Project, *projectGroup.Group)) + d.SetId(*projectGroup.ID) resourceMap, err := projectGroup.ToResourceMap() if err != nil { return diag.FromErr(err) } for key, value := range resourceMap { - d.Set(key, value) + if err := d.Set(key, value); err != nil { + return diag.FromErr(err) + } } } else { logging.Infof("project group belonging to project %s and group %s does not exist", *projectGroup.Project, *projectGroup.Group) diff --git a/oktapam/data_source_server_enrollment_token.go b/oktapam/data_source_server_enrollment_token.go index 7b0344fa2d..c8aef3c445 100644 --- a/oktapam/data_source_server_enrollment_token.go +++ b/oktapam/data_source_server_enrollment_token.go @@ -67,9 +67,11 @@ func dataSourceServerEnrollmentTokenFetch(ctx context.Context, d *schema.Resourc } if token != nil { - d.SetId(createServerEnrollmentTokenResourceID(*token.Project, *token.ID)) + d.SetId(*token.ID) for key, value := range token.ToResourceMap() { - d.Set(key, value) + if err := d.Set(key, value); err != nil { + return diag.FromErr(err) + } } } else { return diag.Errorf("%s %s does not exist", providerServerEnrollmentTokenKey, id) @@ -79,10 +81,6 @@ func dataSourceServerEnrollmentTokenFetch(ctx context.Context, d *schema.Resourc } func getRequiredServerEnrollmentTokenAttributes(d *schema.ResourceData) (string, string, error) { - if d.Id() != "" { - return parseServerEnrollmentTokenResourceID(d.Id()) - } - id := getStringPtr(attributes.ID, d, false) if id == nil { return "", "", fmt.Errorf(errors.MissingAttributeError, attributes.ID) diff --git a/oktapam/resource_ad_task_settings.go b/oktapam/resource_ad_task_settings.go index 3df55431a5..5fbd0aa0a9 100644 --- a/oktapam/resource_ad_task_settings.go +++ b/oktapam/resource_ad_task_settings.go @@ -118,7 +118,7 @@ func resourceADTaskSettings() *schema.Resource { }, }, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: importADTaskSettingsState, }, } } @@ -188,7 +188,7 @@ func resourceADTaskSettingsCreate(ctx context.Context, d *schema.ResourceData, m d.SetId("") } else { //Set returned id - d.SetId(createADTaskSettingsResourceID(adConnID, *createdADTS.ID)) + d.SetId(*createdADTS.ID) } return resourceADTaskSettingsRead(ctx, d, m) @@ -198,13 +198,8 @@ func resourceADTaskSettingsRead(ctx context.Context, d *schema.ResourceData, m i var diags diag.Diagnostics c := m.(client.OktaPAMClient) - adConnID, adTaskSettingsID, err := parseADTaskSettingsResourceID(d.Id()) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set(attributes.ADConnectionID, adConnID); err != nil { - return diag.FromErr(err) - } + adConnID := d.Get(attributes.ADConnectionID).(string) + adTaskSettingsID := d.Id() adTaskSettings, err := c.GetADTaskSettings(ctx, adConnID, adTaskSettingsID) if err != nil { @@ -225,10 +220,9 @@ func resourceADTaskSettingsRead(ctx context.Context, d *schema.ResourceData, m i func resourceADTaskSettingsUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - adConnID, adTaskSettingsID, err := parseADTaskSettingsResourceID(d.Id()) - if err != nil { - return diag.FromErr(err) - } + adConnID := d.Get(attributes.ADConnectionID).(string) + adTaskSettingsID := d.Id() + //If deactivated if _, active := d.GetChange(attributes.IsActive); d.HasChange(attributes.IsActive) && active != nil && !active.(bool) { @@ -257,12 +251,10 @@ func resourceADTaskSettingsDelete(ctx context.Context, d *schema.ResourceData, m var diags diag.Diagnostics c := m.(client.OktaPAMClient) - adConnID, adTaskSettingsID, err := parseADTaskSettingsResourceID(d.Id()) - if err != nil { - return diag.FromErr(err) - } + adConnID := d.Get(attributes.ADConnectionID).(string) + adTaskSettingsID := d.Id() - err = c.DeleteADTaskSettings(ctx, adConnID, adTaskSettingsID) + err := c.DeleteADTaskSettings(ctx, adConnID, adTaskSettingsID) if err != nil { return diag.FromErr(err) } @@ -353,6 +345,9 @@ func expandADRuleAssignments(tfList []interface{}) []*client.ADRuleAssignment { func flattenADTaskSettings(taskSettings *client.ADTaskSettings) map[string]interface{} { m := make(map[string]interface{}, 2) + if taskSettings.ID != nil { + m[attributes.ID] = *taskSettings.ID + } if taskSettings.Name != nil { m[attributes.Name] = *taskSettings.Name } @@ -413,14 +408,27 @@ func flattenADTaskSettings(taskSettings *client.ADTaskSettings) map[string]inter return m } -func createADTaskSettingsResourceID(adConnectionId string, adTaskSettingsId string) string { - return fmt.Sprintf("%s|%s", adConnectionId, adTaskSettingsId) -} - func parseADTaskSettingsResourceID(resourceId string) (string, string, error) { - split := strings.Split(resourceId, "|") + split := strings.Split(resourceId, "/") if len(split) != 2 { - return "", "", fmt.Errorf("oktapam_ad_task_settings id must be in the format of |, received: %s", resourceId) + return "", "", fmt.Errorf("expected format: /, received: %s", resourceId) } return split[0], split[1], nil } + +func importADTaskSettingsState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // d.Id() here is the last argument passed to the `terraform import RESOURCE_TYPE.RESOURCE_NAME RESOURCE_ID` command + // Id provided for import is in the format / + adConnectionID, adTaskSettingsID, err := parseADTaskSettingsResourceID(d.Id()) + + if err != nil { + return nil, fmt.Errorf("invalid resource import specifier; %w", err) + } + + if err := d.Set(attributes.ADConnectionID, adConnectionID); err != nil { + return nil, err + } + + d.SetId(adTaskSettingsID) + return []*schema.ResourceData{d}, nil +} \ No newline at end of file diff --git a/oktapam/resource_ad_task_settings_test.go b/oktapam/resource_ad_task_settings_test.go index 37fd25f36a..229c16792f 100644 --- a/oktapam/resource_ad_task_settings_test.go +++ b/oktapam/resource_ad_task_settings_test.go @@ -98,6 +98,7 @@ func TestAccADTaskSettings(t *testing.T) { ResourceName: adTaskResourceName, ImportState: true, ImportStateVerify: true, + ImportStateIdFunc: testAccADTaskSettingsImportStateId(adTaskResourceName), }, }, }) @@ -109,10 +110,8 @@ func testAccADTaskCheckExists(adTaskSettingsResourceName string) resource.TestCh if !ok { return fmt.Errorf("resource not found: %s", adTaskSettingsResourceName) } - adConnID, adTaskSettingsID, err := parseADTaskSettingsResourceID(adTaskRS.Primary.ID) - if err != nil { - return err - } + adConnID := adTaskRS.Primary.Attributes[attributes.ADConnectionID] + adTaskSettingsID := adTaskRS.Primary.ID pamClient := testAccProvider.Meta().(client.OktaPAMClient) adTaskSettings, err := pamClient.GetADTaskSettings(context.Background(), adConnID, adTaskSettingsID) @@ -134,10 +133,8 @@ func testAccADTaskCheckDestroy(adTaskSettingsResourceName string) resource.TestC return fmt.Errorf("resource not found: %s", adTaskSettingsResourceName) } - adConnID, adTaskSettingsID, err := parseADTaskSettingsResourceID(adTaskRS.Primary.ID) - if err != nil { - return err - } + adConnID := adTaskRS.Primary.Attributes[attributes.ADConnectionID] + adTaskSettingsID := adTaskRS.Primary.ID pamClient := testAccProvider.Meta().(client.OktaPAMClient) adTask, err := pamClient.GetADTaskSettings(context.Background(), adConnID, adTaskSettingsID) @@ -200,7 +197,7 @@ resource "oktapam_ad_task_settings" "test_acc_ad_task_settings" { rule_assignments { base_dn = "dc=example,dc=com" ldap_query_filter = "(objectclass=computer)" - project_id = oktapam_project.test_acc_project.project_id + project_id = oktapam_project.test_acc_project.id priority = 1 } } @@ -224,7 +221,7 @@ resource "oktapam_ad_task_settings" "test_acc_ad_task_settings" { rule_assignments { base_dn = "dc=example,dc=com" ldap_query_filter = "(objectclass=computer)" - project_id = oktapam_project.test_acc_project.project_id + project_id = oktapam_project.test_acc_project.id priority = 1 } } @@ -249,7 +246,7 @@ resource "oktapam_ad_task_settings" "test_acc_ad_task_settings" { rule_assignments { base_dn = "dc=example,dc=com" ldap_query_filter = "(objectclass=computer)" - project_id = oktapam_project.test_acc_project.project_id + project_id = oktapam_project.test_acc_project.id priority = 1 } } @@ -274,7 +271,7 @@ resource "oktapam_ad_task_settings" "test_acc_ad_task_settings" { rule_assignments { base_dn = "dc=example,dc=com" ldap_query_filter = "(objectclass=computer)" - project_id = oktapam_project.test_acc_project.project_id + project_id = oktapam_project.test_acc_project.id priority = 1 } additional_attribute_mapping { @@ -304,13 +301,13 @@ resource "oktapam_ad_task_settings" "test_acc_ad_task_settings" { rule_assignments { base_dn = "dc=example,dc=com" ldap_query_filter = "(objectclass=computer)" - project_id = oktapam_project.test_acc_project.project_id + project_id = oktapam_project.test_acc_project.id priority = 1 } rule_assignments { base_dn = "dc=example,dc=com" ldap_query_filter = "(objectclass=computer)" - project_id = oktapam_project.test_acc_project.project_id + project_id = oktapam_project.test_acc_project.id priority = 2 } @@ -322,3 +319,13 @@ func createTestAccADTaskSettingsUpdateRulesConfig(preConfig string, adTaskName s return preConfig + fmt.Sprintf(testAccADTaskUpdateRulesConfigFormat, adTaskName) } + +func testAccADTaskSettingsImportStateId(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + return fmt.Sprintf("%s/%s", rs.Primary.Attributes[attributes.ADConnectionID], rs.Primary.Attributes[attributes.ID]), nil + } +} diff --git a/oktapam/resource_group.go b/oktapam/resource_group.go index 82aacf1b96..41cfde75a0 100644 --- a/oktapam/resource_group.go +++ b/oktapam/resource_group.go @@ -49,7 +49,7 @@ func resourceGroup() *schema.Resource { }, }, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: importResourceGroupState, }, } } @@ -78,15 +78,15 @@ func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, m interfac if err != nil { return diag.FromErr(err) } - d.SetId(*group.Name) + return resourceGroupRead(ctx, d, m) } func resourceGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics c := m.(client.OktaPAMClient) - groupName := d.Id() + //Get group api require group name not ASA Group UUID to read resource back + groupName := d.Get(attributes.Name).(string) group, err := c.GetGroup(ctx, groupName, false) if err != nil { return diag.FromErr(err) @@ -95,24 +95,27 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, m interface{ if group != nil && utils.IsNonEmpty(group.Name) { if utils.IsBlank(group.DeletedAt) { logging.Infof("Group %s exists", *group.Name) - d.SetId(*group.Name) + d.SetId(*group.ID) } else { logging.Infof("Group %s was removed", groupName) d.SetId("") } for key, value := range group.ToResourceMap() { - d.Set(key, value) + if err := d.Set(key, value); err != nil { + return diag.FromErr(err) + } } } else { logging.Infof("Group %s does not exist", groupName) + d.SetId("") } - return diags + return nil } func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - groupName := d.Id() + groupName := d.Get(attributes.Name).(string) changed := false updates := make(map[string]interface{}) @@ -146,7 +149,7 @@ func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, m interfac func resourceGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics c := m.(client.OktaPAMClient) - groupName := d.Id() + groupName := d.Get(attributes.Name).(string) err := c.DeleteGroup(ctx, groupName) if err != nil { @@ -156,3 +159,12 @@ func resourceGroupDelete(ctx context.Context, d *schema.ResourceData, m interfac d.SetId("") return diags } + +func importResourceGroupState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // d.Id() here is the last argument passed to the `terraform import RESOURCE_TYPE.RESOURCE_NAME RESOURCE_ID` command + // Set the passed id as group name to make read work + if err := d.Set(attributes.Name, d.Id()); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} diff --git a/oktapam/resource_group_test.go b/oktapam/resource_group_test.go index a20b712db6..d175d12605 100644 --- a/oktapam/resource_group_test.go +++ b/oktapam/resource_group_test.go @@ -52,6 +52,7 @@ func TestAccGroup(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateIdFunc: testAccGroupImportStateId(resourceName), }, }, }) @@ -68,9 +69,6 @@ func testAccGroupCheckExists(rn string, expectedGroup client.Group) resource.Tes if resourceID == "" { return fmt.Errorf("resource id not set") } - if *expectedGroup.Name != resourceID { - return fmt.Errorf("resource id not set to expected value. expected %s, got %s", *expectedGroup.Name, resourceID) - } client := testAccProvider.Meta().(client.OktaPAMClient) group, err := client.GetGroup(context.Background(), *expectedGroup.Name, false) @@ -126,3 +124,13 @@ resource "oktapam_group" "test_group" { func createTestAccGroupUpdateConfig(groupName string) string { return fmt.Sprintf(testAccGroupUpdateConfigFormat, groupName) } + +func testAccGroupImportStateId(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + return rs.Primary.Attributes[attributes.Name], nil + } +} diff --git a/oktapam/resource_project.go b/oktapam/resource_project.go index bb471f5081..4d71d18e2e 100644 --- a/oktapam/resource_project.go +++ b/oktapam/resource_project.go @@ -35,11 +35,6 @@ func resourceProject() *schema.Resource { Computed: true, // Description is autogenerated }, - attributes.ProjectID: { - Type: schema.TypeString, - Computed: true, - Description: descriptions.ProjectID, - }, attributes.Team: { Type: schema.TypeString, Computed: true, @@ -159,14 +154,15 @@ func resourceProjectCreate(ctx context.Context, d *schema.ResourceData, m interf return diag.FromErr(err) } - d.SetId(*project.Name) return resourceProjectRead(ctx, d, m) } func resourceProjectReadWithIgnorable(ctx context.Context, d *schema.ResourceData, m interface{}, ignoreValues bool) (*schema.ResourceData, error) { c := m.(client.OktaPAMClient) - projectName := d.Id() + //Get project api require project name not ASA Project UUID to read resource back + projectName := d.Get(attributes.Name).(string) + proj, err := c.GetProject(ctx, projectName, false) if err != nil { return nil, err @@ -179,7 +175,7 @@ func resourceProjectReadWithIgnorable(ctx context.Context, d *schema.ResourceDat if proj != nil && utils.IsNonEmpty(proj.Name) { if utils.IsBlank(proj.DeletedAt) { logging.Infof("Project %s exists", proj.Name) - d.SetId(*proj.Name) + d.SetId(*proj.ID) } else { logging.Infof("Project %s was removed", projectName) d.SetId("") @@ -198,6 +194,11 @@ func resourceProjectReadWithIgnorable(ctx context.Context, d *schema.ResourceDat } func resourceProjectReadImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + // d.Id() here is the last argument passed to the `terraform import RESOURCE_TYPE.RESOURCE_NAME RESOURCE_ID` command + // Set the passed id as project name to make read work + if err := d.Set(attributes.Name, d.Id()); err != nil { + return nil, err + } projectResource, err := resourceProjectReadWithIgnorable(ctx, d, m, false) if err != nil { return nil, err @@ -215,7 +216,7 @@ func resourceProjectRead(ctx context.Context, d *schema.ResourceData, m interfac func resourceProjectUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - projectName := d.Id() + projectName := d.Get(attributes.Name).(string) changed := false updates := make(map[string]interface{}) @@ -251,7 +252,7 @@ func resourceProjectUpdate(ctx context.Context, d *schema.ResourceData, m interf func resourceProjectDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var diags diag.Diagnostics c := m.(client.OktaPAMClient) - projectName := d.Id() + projectName := d.Get(attributes.Name).(string) err := c.DeleteProject(ctx, projectName) if err != nil { diff --git a/oktapam/resource_project_group.go b/oktapam/resource_project_group.go index cbe9982204..0db1c8c4e6 100644 --- a/oktapam/resource_project_group.go +++ b/oktapam/resource_project_group.go @@ -23,6 +23,11 @@ func resourceProjectGroup() *schema.Resource { UpdateContext: resourceProjectGroupUpdate, DeleteContext: resourceProjectGroupDelete, Schema: map[string]*schema.Schema{ + attributes.ID: { + Type: schema.TypeString, + Computed: true, + // Description is autogenerated + }, attributes.ProjectName: { Type: schema.TypeString, Required: true, @@ -73,7 +78,7 @@ func resourceProjectGroup() *schema.Resource { }, }, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: importResourceProjectGroupState, }, } } @@ -123,22 +128,16 @@ func resourceProjectGroupCreate(ctx context.Context, d *schema.ResourceData, m i return diag.FromErr(err) } - d.SetId(createProjectGroupResourceID(*projectGroup.Project, *projectGroup.Group)) - return resourceProjectGroupRead(ctx, d, m) } func resourceProjectGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - project, group, err := parseProjectGroupResourceID(d.Id()) - if err != nil { - return diag.FromErr(err) - } - if project == "" { - return diag.Errorf("blank project name from id %s", d.Id()) - } - projectGroup, err := c.GetProjectGroup(ctx, project, group) + projectName := d.Get(attributes.ProjectName).(string) + groupName := d.Get(attributes.GroupName).(string) + + projectGroup, err := c.GetProjectGroup(ctx, projectName, groupName) if err != nil { return diag.FromErr(err) } @@ -146,18 +145,18 @@ func resourceProjectGroupRead(ctx context.Context, d *schema.ResourceData, m int ignorableValues := map[string]bool{attributes.DeletedAt: true, attributes.RemovedAt: true} if projectGroup != nil && utils.IsNonEmpty(projectGroup.Group) { if utils.IsNonEmpty(projectGroup.DeletedAt) { - logging.Infof("Group %s was deleted", group) + logging.Infof("Group %s was deleted", groupName) d.SetId("") } else if utils.IsNonEmpty(projectGroup.RemovedAt) { - logging.Infof("Group %s was removed from project %s", group, project) + logging.Infof("Group %s was removed from project %s", groupName, projectName) d.SetId("") } else { - d.SetId(createProjectGroupResourceID(*projectGroup.Project, *projectGroup.Group)) attrs, err := projectGroup.ToResourceMap() if err != nil { return diag.FromErr(err) } + d.SetId(*projectGroup.ID) for key, value := range attrs { if _, ok := ignorableValues[key]; !ok { if err := d.Set(key, value); err != nil { @@ -167,7 +166,7 @@ func resourceProjectGroupRead(ctx context.Context, d *schema.ResourceData, m int } } } else { - logging.Infof("Group %s is not assigned to project %s", group, project) + logging.Infof("Group %s is not assigned to project %s", groupName, projectName) d.SetId("") } @@ -223,12 +222,11 @@ func resourceProjectGroupUpdate(ctx context.Context, d *schema.ResourceData, m i func resourceProjectGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - project, group, err := parseProjectGroupResourceID(d.Id()) - if err != nil { - return diag.FromErr(err) - } - err = c.DeleteProjectGroup(ctx, project, group) + project := d.Get(attributes.ProjectName).(string) + group := d.Get(attributes.GroupName).(string) + + err := c.DeleteProjectGroup(ctx, project, group) if err != nil { return diag.FromErr(err) } @@ -237,14 +235,30 @@ func resourceProjectGroupDelete(ctx context.Context, d *schema.ResourceData, m i return nil } -func createProjectGroupResourceID(project string, group string) string { - return fmt.Sprintf("%s|%s", project, group) -} - func parseProjectGroupResourceID(resourceId string) (string, string, error) { - split := strings.Split(resourceId, "|") + split := strings.Split(resourceId, "/") if len(split) != 2 { - return "", "", fmt.Errorf("oktapam_project_group id must be in the format of |, received: %s", resourceId) + return "", "", fmt.Errorf("expected format: /, received: %s", resourceId) } return split[0], split[1], nil } + + +func importResourceProjectGroupState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // d.Id() here is the last argument passed to the `terraform import RESOURCE_TYPE.RESOURCE_NAME RESOURCE_ID` command + // Id provided for import is in the format / + projectName, groupName, err := parseProjectGroupResourceID(d.Id()) + + if err != nil { + return nil, fmt.Errorf("invalid resource import specifier; %w", err) + } + + if err := d.Set(attributes.ProjectName, projectName); err != nil { + return nil, err + } + if err := d.Set(attributes.GroupName, groupName); err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} diff --git a/oktapam/resource_project_group_test.go b/oktapam/resource_project_group_test.go index e820510148..5ca697dfef 100644 --- a/oktapam/resource_project_group_test.go +++ b/oktapam/resource_project_group_test.go @@ -80,8 +80,11 @@ func TestAccProjectGroup(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, + ResourceName: resourceName, + ImportState: true, + //Used to dynamically generate the ID from the terraform state. + //Terraform Resource ID is set to ASA ProjectGroup UUID but read requires "Project Name" and "Group Name" to retrieve the resource + ImportStateIdFunc: testAccProjectGroupImportStateId(resourceName), ImportStateVerify: true, }, }, @@ -95,11 +98,8 @@ func testAccProjectGroupCheckExists(rn string, expectedProjectGroup client.Proje return fmt.Errorf("resource not found: %s", rn) } - resourceID := rs.Primary.ID - project, group, err := parseProjectGroupResourceID(resourceID) - if err != nil { - return fmt.Errorf("error parsing resource id: %w", err) - } + project := rs.Primary.Attributes[attributes.ProjectName] + group := rs.Primary.Attributes[attributes.GroupName] pamClient := testAccProvider.Meta().(client.OktaPAMClient) projectGroup, err := pamClient.GetProjectGroup(context.Background(), project, group) @@ -109,6 +109,8 @@ func testAccProjectGroupCheckExists(rn string, expectedProjectGroup client.Proje return fmt.Errorf("project group does not exist") } + //"ID" is computed after resource creation, so make it same to avoid comparison diff. + expectedProjectGroup.ID = projectGroup.ID comparison := pretty.Compare(expectedProjectGroup, projectGroup) if comparison != "" { return fmt.Errorf("expected project group does not match returned project.\n%s", comparison) @@ -186,3 +188,13 @@ resource "oktapam_project_group" "test_acc_project_group" { func createTestAccProjectGroupUpdateConfig(projectGroup client.ProjectGroup) string { return fmt.Sprintf(testAccProjectGroupUpdateConfigFormat, *projectGroup.Project, *projectGroup.Group) } + +func testAccProjectGroupImportStateId(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + return fmt.Sprintf("%s/%s", rs.Primary.Attributes[attributes.ProjectName], rs.Primary.Attributes[attributes.GroupName]), nil + } +} diff --git a/oktapam/resource_project_test.go b/oktapam/resource_project_test.go index e987c69832..274ae78bf0 100644 --- a/oktapam/resource_project_test.go +++ b/oktapam/resource_project_test.go @@ -85,6 +85,7 @@ func TestAccProject(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateIdFunc: testAccProjectImportStateId(resourceName), }, }, }) @@ -101,9 +102,6 @@ func testAccProjectCheckExists(rn string, expectedProject client.Project) resour if resourceID == "" { return fmt.Errorf("resource id not set") } - if *expectedProject.Name != resourceID { - return fmt.Errorf("resource id not set to expected value. expected %s, got %s", *expectedProject.Name, resourceID) - } client := testAccProvider.Meta().(client.OktaPAMClient) proj, err := client.GetProject(context.Background(), *expectedProject.Name, false) @@ -167,3 +165,13 @@ resource "oktapam_project" "test_project" { func createTestAccProjectUpdateConfig(projectName string) string { return fmt.Sprintf(testAccProjectUpdateConfigFormat, projectName) } + +func testAccProjectImportStateId(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + return rs.Primary.Attributes[attributes.Name], nil + } +} diff --git a/oktapam/resource_server_enrollment_token.go b/oktapam/resource_server_enrollment_token.go index 903cca7dac..b6750580d2 100644 --- a/oktapam/resource_server_enrollment_token.go +++ b/oktapam/resource_server_enrollment_token.go @@ -56,7 +56,7 @@ func resourceServerEnrollmentToken() *schema.Resource { }, }, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: importResourceServerEnrollmentTokenState, }, } } @@ -64,13 +64,10 @@ func resourceServerEnrollmentToken() *schema.Resource { func resourceServerEnrollmentTokenRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - resourceId := d.Id() - project, id, err := parseServerEnrollmentTokenResourceID(resourceId) - if err != nil { - return diag.FromErr(err) - } + serverEnrollmentTokenID := d.Id() + projectName := d.Get(attributes.ProjectName).(string) - token, err := c.GetServerEnrollmentToken(ctx, project, id) + token, err := c.GetServerEnrollmentToken(ctx, projectName, serverEnrollmentTokenID) if err != nil { return diag.FromErr(err) } @@ -83,7 +80,9 @@ func resourceServerEnrollmentTokenRead(ctx context.Context, d *schema.ResourceDa for key, value := range token.ToResourceMap() { logging.Debugf("setting %s to %v", key, value) - d.Set(key, value) + if err := d.Set(key, value); err != nil { + return diag.FromErr(err) + } } return nil @@ -92,15 +91,15 @@ func resourceServerEnrollmentTokenRead(ctx context.Context, d *schema.ResourceDa func resourceServerEnrollmentTokenCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - project := d.Get(attributes.ProjectName).(string) + projectName := d.Get(attributes.ProjectName).(string) description := d.Get(attributes.Description).(string) - token, err := c.CreateServerEnrollmentToken(ctx, project, description) + token, err := c.CreateServerEnrollmentToken(ctx, projectName, description) if err != nil { return diag.FromErr(err) } - d.SetId(createServerEnrollmentTokenResourceID(*token.Project, *token.ID)) + d.SetId(*token.ID) return resourceServerEnrollmentTokenRead(ctx, d, m) } @@ -108,13 +107,10 @@ func resourceServerEnrollmentTokenCreate(ctx context.Context, d *schema.Resource func resourceServerEnrollmentTokenDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { c := m.(client.OktaPAMClient) - resourceId := d.Id() - project, id, err := parseServerEnrollmentTokenResourceID(resourceId) - if err != nil { - return diag.FromErr(err) - } + serverEnrollmentTokenID := d.Id() + projectName := d.Get(attributes.ProjectName).(string) - err = c.DeleteServerEnrollmentToken(ctx, project, id) + err := c.DeleteServerEnrollmentToken(ctx, projectName, serverEnrollmentTokenID) if err != nil { return diag.FromErr(err) } @@ -124,14 +120,28 @@ func resourceServerEnrollmentTokenDelete(ctx context.Context, d *schema.Resource return nil } -func createServerEnrollmentTokenResourceID(project string, tokenID string) string { - return fmt.Sprintf("%s|%s", project, tokenID) -} - func parseServerEnrollmentTokenResourceID(resourceId string) (string, string, error) { - split := strings.Split(resourceId, "|") + split := strings.Split(resourceId, "/") if len(split) != 2 { - return "", "", fmt.Errorf("oktapam_server_enrollment_token id must be in the format of |, received: %s", resourceId) + return "", "", fmt.Errorf("expected format: /, received: %s", resourceId) } return split[0], split[1], nil } + +func importResourceServerEnrollmentTokenState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // d.Id() here is the last argument passed to the `terraform import RESOURCE_TYPE.RESOURCE_NAME RESOURCE_ID` command + // Id provided for import is in the format /. + // Both project name and ASA resource UUID is required to read resource back + projectName, serverEnrollmentTokenID, err := parseServerEnrollmentTokenResourceID(d.Id()) + if err != nil { + return nil, fmt.Errorf("invalid resource import specifier; %w", err) + } + + if err := d.Set(attributes.ProjectName, projectName); err != nil { + return nil, err + } + //Set id to ASA resource UUID + d.SetId(serverEnrollmentTokenID) + + return []*schema.ResourceData{d}, nil +} diff --git a/oktapam/resource_server_enrollment_token_test.go b/oktapam/resource_server_enrollment_token_test.go index 9e90f873c8..713aaabe1e 100644 --- a/oktapam/resource_server_enrollment_token_test.go +++ b/oktapam/resource_server_enrollment_token_test.go @@ -44,6 +44,7 @@ func TestAccServerEnrollmentToken(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, + ImportStateIdFunc: testAccServerEnrollmentTokenImportStateId(resourceName), }, }, }) @@ -56,22 +57,19 @@ func testAccServerEnrollmentTokenCheckExists(rn string, expectedServerEnrollment return fmt.Errorf("resource not found: %s", rn) } - resourceID := rs.Primary.ID - projectName, tokenID, err := parseServerEnrollmentTokenResourceID(resourceID) - if err != nil { - return fmt.Errorf("error parsing resource id: %w", err) - } + serverEnrollmentTokenId := rs.Primary.ID + projectName := rs.Primary.Attributes[attributes.ProjectName] if projectName != *expectedServerEnrollmentToken.Project { return fmt.Errorf("resource id did not have the expected project. expected %s, got %s", *expectedServerEnrollmentToken.Project, projectName) } client := testAccProvider.Meta().(client.OktaPAMClient) - token, err := client.GetServerEnrollmentToken(context.Background(), projectName, tokenID) + token, err := client.GetServerEnrollmentToken(context.Background(), projectName, serverEnrollmentTokenId) if err != nil { return fmt.Errorf("error getting server enrollment token: %w", err) } if token == nil || utils.IsBlank(token.ID) { - return fmt.Errorf("server enrollment token for project %s with id %s does not exist", projectName, tokenID) + return fmt.Errorf("server enrollment token for project %s with id %s does not exist", projectName, serverEnrollmentTokenId) } if *token.Description != *expectedServerEnrollmentToken.Description { return fmt.Errorf("expected description does not match returned description for server enrollment token. expected: %s, got: %s", *expectedServerEnrollmentToken.Description, *token.Description) @@ -124,3 +122,13 @@ resource "oktapam_server_enrollment_token" "test_server_enrollment_token" { func createTestAccServerEnrollmentTokenCreateConfig(token client.ServerEnrollmentToken) string { return fmt.Sprintf(testAccServerEnrollmentTokenCreateConfigFormat, *token.Project, *token.Description) } + +func testAccServerEnrollmentTokenImportStateId(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + return fmt.Sprintf("%s/%s", rs.Primary.Attributes[attributes.ProjectName], rs.Primary.ID), nil + } +} diff --git a/oktapam/resource_user.go b/oktapam/resource_user.go index 3e075be283..84c2bb45d9 100644 --- a/oktapam/resource_user.go +++ b/oktapam/resource_user.go @@ -26,6 +26,11 @@ func resourceUser() *schema.Resource { UpdateContext: resourceUserUpdate, DeleteContext: resourceUserDelete, Schema: map[string]*schema.Schema{ + attributes.ID: { + Type: schema.TypeString, + Computed: true, + // Description is autogenerated + }, attributes.Name: { Type: schema.TypeString, Required: true, @@ -72,7 +77,7 @@ func resourceUser() *schema.Resource { }, }, Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, + StateContext: importResourceUserState, }, } } @@ -97,7 +102,6 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, m interface return diag.FromErr(err) } - d.SetId(createUserID(userName, userType)) return resourceUserRead(ctx, d, m) } @@ -123,11 +127,12 @@ func resourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{} } if user != nil { - d.SetId(createUserID(userName, userType)) + d.SetId(*user.ID) for key, value := range user.ToResourceMap() { d.Set(key, value) } } else { + d.SetId("") logging.Infof("%s user %s does not exist", userType, userName) } @@ -227,10 +232,6 @@ func resourceUserDelete(ctx context.Context, d *schema.ResourceData, m interface } func getRequiredUserAttributes(d *schema.ResourceData) (string, string, error) { - if d.Id() != "" { - return parseUserID(d.Id()) - } - userName := getStringPtr(attributes.Name, d, false) if userName == nil { return "", "", fmt.Errorf(errors.MissingAttributeError, attributes.Name) @@ -245,13 +246,32 @@ func getRequiredUserAttributes(d *schema.ResourceData) (string, string, error) { } func createUserID(userName string, userType string) string { - return fmt.Sprintf("%s|%s", userName, userType) + return fmt.Sprintf("%s/%s", userName, userType) } func parseUserID(resourceId string) (string, string, error) { - split := strings.Split(resourceId, "|") + split := strings.Split(resourceId, "/") if len(split) != 2 { - return "", "", fmt.Errorf("oktapam_user id must be in the format of |, received: %s", resourceId) + return "", "", fmt.Errorf("expected format: /, received: %s", resourceId) } return split[0], split[1], nil } + +func importResourceUserState(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // d.Id() here is the last argument passed to the `terraform import RESOURCE_TYPE.RESOURCE_NAME RESOURCE_ID` command + // Id provided for import is in the format / + userName, userType, err := parseUserID(d.Id()) + + if err != nil { + return nil, fmt.Errorf("invalid resource import specifier; %w", err) + } + + if err := d.Set(attributes.Name, userName); err != nil { + return nil, err + } + if err := d.Set(attributes.UserType, userType); err != nil { + return nil, err + } + + return []*schema.ResourceData{d}, nil +} diff --git a/oktapam/resource_user_test.go b/oktapam/resource_user_test.go index e9fd1cac11..9f1f837b9a 100644 --- a/oktapam/resource_user_test.go +++ b/oktapam/resource_user_test.go @@ -58,8 +58,11 @@ func TestAccUser(t *testing.T) { ), }, { - ResourceName: resourceName1, - ImportState: true, + ResourceName: resourceName1, + ImportState: true, + //Used to dynamically generate the ID from the terraform state. + //Terraform Resource ID is set to ASA User UUID but read requires "User Name" and "Type" to retrieve the resource + ImportStateIdFunc: testAccUserImportStateId(resourceName1), ImportStateVerify: true, }, { @@ -113,10 +116,6 @@ func testAccServiceUserCheckExists(rn string, expectedUser client.User) resource return fmt.Errorf("resource id not set") } - if expectedUser.Name == nil || createUserID(*expectedUser.Name, expectedUser.UserType.String()) != resourceID { - return fmt.Errorf("resource id not set to expected value. expected %s, got %s", *expectedUser.Name, resourceID) - } - c := testAccProvider.Meta().(client.OktaPAMClient) user, err := c.GetServiceUser(context.Background(), *expectedUser.Name) if err != nil { @@ -126,6 +125,7 @@ func testAccServiceUserCheckExists(rn string, expectedUser client.User) resource return fmt.Errorf("service user %s does not exist", *expectedUser.Name) } expectedUser.DeletedAt = user.DeletedAt + expectedUser.ID = user.ID comparison := pretty.Compare(user, expectedUser) if comparison != "" { return fmt.Errorf("expected service user does not match returned service user.\n%s", comparison) @@ -171,3 +171,13 @@ resource "oktapam_user" "%s" { user_type = "%s" } ` + +func testAccUserImportStateId(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } + return fmt.Sprintf("%s/%s", rs.Primary.Attributes[attributes.Name], rs.Primary.Attributes[attributes.UserType]), nil + } +}