Terraform: convert role to generic provider (#69397)

Contributes to https://github.com/gravitational/teleport/issues/69301.
This commit is contained in:
rosstimothy
2026-08-07 17:23:39 +00:00
committed by GitHub
parent 01380b6c39
commit e98fa3e28a
7 changed files with 143 additions and 514 deletions
-18
View File
@@ -383,22 +383,6 @@ var (
HasCheckAndSetDefaults: true,
}
role = payload{
Name: "Role",
TypeName: "RoleV6",
VarName: "role",
GetMethod: "GetRole",
CreateMethod: "CreateRole",
UpdateMethod: "UpsertRole",
UpsertMethodArity: 2,
DeleteMethod: "DeleteRole",
ID: "role.Metadata.Name",
Kind: "role",
HasStaticID: false,
TerraformResourceType: "teleport_role",
HasCheckAndSetDefaults: true,
}
sessionRecording = payload{
Name: "SessionRecordingConfig",
TypeName: "SessionRecordingConfigV2",
@@ -1031,8 +1015,6 @@ func genTFSchema() {
generateDataSource(samlIdPServiceProvider, pluralDataSource)
generateResource(provisionToken, pluralResource)
generateDataSource(provisionToken, pluralDataSource)
generateResource(role, pluralResource)
generateDataSource(role, pluralDataSource)
generateResource(trustedCluster, pluralResource)
generateDataSource(trustedCluster, pluralDataSource)
generateResource(sessionRecording, singularResource)
@@ -1,97 +0,0 @@
// Code generated by _gen/main.go DO NOT EDIT
/*
Copyright 2015-2024 Gravitational, Inc.
Licensed under the Apache 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://www.apache.org/licenses/LICENSE-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 legacy
import (
"context"
apitypes "github.com/gravitational/teleport/api/types"
"github.com/gravitational/trace"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/gravitational/teleport/integrations/terraform/tfschema"
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
)
// dataSourceTeleportRoleType is the data source metadata type
type dataSourceTeleportRoleType struct{}
// dataSourceTeleportRole is the resource
type dataSourceTeleportRole struct {
p Provider
}
// GetSchema returns the data source schema
func (r dataSourceTeleportRoleType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfschema.GenSchemaRoleV6(ctx)
}
// NewDataSource creates the empty data source
func (r dataSourceTeleportRoleType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) {
return dataSourceTeleportRole{
p: p.(Provider),
}, nil
}
// Read reads teleport Role
func (r dataSourceTeleportRole) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) {
var id types.String
diags := req.Config.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
roleI, err := r.p.Client().GetRole(ctx, id.Value)
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Wrap(err), "role"))
return
}
var state types.Object
resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Todo: Remove after updating terraform-plugin to >=v1.5.0.
// terraform-plugin-testing version <1.5.0 requires data resources to
// implement the 'id' attribute.
// https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes
v, ok := state.Attrs["id"]
if !ok || v.IsNull() {
state.Attrs["id"] = id
}
role := roleI.(*apitypes.RoleV6)
diags = tfschema.CopyRoleV6ToTerraform(ctx, role, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
@@ -29,7 +29,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType {
"teleport_github_connector": dataSourceTeleportGithubConnectorType{},
"teleport_provision_token": dataSourceTeleportProvisionTokenType{},
"teleport_oidc_connector": dataSourceTeleportOIDCConnectorType{},
"teleport_role": dataSourceTeleportRoleType{},
"teleport_saml_idp_service_provider": dataSourceTeleportSAMLIdPServiceProviderType{},
"teleport_session_recording_config": dataSourceTeleportSessionRecordingConfigType{},
"teleport_trusted_cluster": dataSourceTeleportTrustedClusterType{},
@@ -69,7 +68,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType {
"teleport_github_connector": resourceTeleportGithubConnectorType{},
"teleport_provision_token": resourceTeleportProvisionTokenType{},
"teleport_oidc_connector": resourceTeleportOIDCConnectorType{},
"teleport_role": resourceTeleportRoleType{},
"teleport_saml_idp_service_provider": resourceTeleportSAMLIdPServiceProviderType{},
"teleport_session_recording_config": resourceTeleportSessionRecordingConfigType{},
"teleport_trusted_cluster": resourceTeleportTrustedClusterType{},
@@ -1,397 +0,0 @@
// Code generated by _gen/main.go DO NOT EDIT
/*
Copyright 2015-2024 Gravitational, Inc.
Licensed under the Apache 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://www.apache.org/licenses/LICENSE-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 legacy
import (
"context"
"fmt"
apitypes "github.com/gravitational/teleport/api/types"
"github.com/gravitational/trace"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/gravitational/teleport/integrations/terraform/tfschema"
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
)
// resourceTeleportRoleType is the resource metadata type
type resourceTeleportRoleType struct{}
// resourceTeleportRole is the resource
type resourceTeleportRole struct {
p Provider
}
// GetSchema returns the resource schema
func (r resourceTeleportRoleType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
return tfschema.GenSchemaRoleV6(ctx)
}
// NewResource creates the empty resource
func (r resourceTeleportRoleType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) {
return resourceTeleportRole{
p: p.(Provider),
}, nil
}
// Create creates the Role
func (r resourceTeleportRole) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) {
var err error
if !r.p.IsConfigured(resp.Diagnostics) {
return
}
var plan types.Object
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
role := &apitypes.RoleV6{}
diags = tfschema.CopyRoleV6FromTerraform(ctx, plan, role)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
roleResource := role
err = roleResource.CheckAndSetDefaults()
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting Role defaults", trace.Wrap(err), "role"))
return
}
id := roleResource.Metadata.Name
_, err = r.p.Client().GetRole(ctx, id)
if !trace.IsNotFound(err) {
if err == nil {
existErr := fmt.Sprintf("Role exists in Teleport. Either remove it (tctl rm role/%v)"+
" or import it to the existing state (terraform import teleport_role.%v %v)", id, id, id)
resp.Diagnostics.Append(tfdiag.DiagFromErr("Role exists in Teleport", trace.Errorf(existErr)))
return
}
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Wrap(err), "role"))
return
}
_, err = r.p.Client().CreateRole(ctx, roleResource)
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating Role", trace.Wrap(err), "role"))
return
}
// Not really an inferface, just using the same name for easier templating.
var roleI apitypes.Role
// Try getting the resource until it exists.
tries := 0
retry, err := r.p.Retry()
if err != nil {
return
}
for {
tries = tries + 1
roleI, err = r.p.Client().GetRole(ctx, id)
if trace.IsNotFound(err) {
select {
case <-ctx.Done():
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Wrap(ctx.Err()), "role"))
return
case <-retry.After():
}
if tries >= r.p.MaxRetries() {
diagMessage := fmt.Sprintf("Error reading Role (tried %d times) - state outdated, please import resource", tries)
resp.Diagnostics.AddError(diagMessage, "role")
return
}
continue
}
break
}
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Wrap(err), "role"))
return
}
roleResource, ok := roleI.(*apitypes.RoleV6)
if !ok {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Errorf("Can not convert %T to RoleV6", roleI), "role"))
return
}
role = roleResource
diags = tfschema.CopyRoleV6ToTerraform(ctx, role, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
plan.Attrs["id"] = types.String{Value: role.Metadata.Name}
diags = resp.State.Set(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Read reads teleport Role
func (r resourceTeleportRole) Read(ctx context.Context, req tfsdk.ReadResourceRequest, resp *tfsdk.ReadResourceResponse) {
var state types.Object
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
var id types.String
diags = req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
roleI, err := r.p.Client().GetRole(ctx, id.Value)
if trace.IsNotFound(err) {
resp.State.RemoveResource(ctx)
return
}
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Wrap(err), "role"))
return
}
role := roleI.(*apitypes.RoleV6)
diags = tfschema.CopyRoleV6ToTerraform(ctx, role, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Update updates teleport Role
func (r resourceTeleportRole) Update(ctx context.Context, req tfsdk.UpdateResourceRequest, resp *tfsdk.UpdateResourceResponse) {
if !r.p.IsConfigured(resp.Diagnostics) {
return
}
var plan types.Object
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
role := &apitypes.RoleV6{}
diags = tfschema.CopyRoleV6FromTerraform(ctx, plan, role)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
roleResource := role
if err := roleResource.CheckAndSetDefaults(); err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Role", err, "role"))
return
}
name := roleResource.Metadata.Name
roleBefore, err := r.p.Client().GetRole(ctx, name)
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", err, "role"))
return
}
_, err = r.p.Client().UpsertRole(ctx, roleResource)
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Role", err, "role"))
return
}
// Not really an inferface, just using the same name for easier templating.
var roleI apitypes.Role
tries := 0
retry, err := r.p.Retry()
if err != nil {
return
}
for {
tries = tries + 1
roleI, err = r.p.Client().GetRole(ctx, name)
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", err, "role"))
return
}
if roleBefore.GetMetadata().Revision != roleI.GetMetadata().Revision || false {
break
}
select {
case <-ctx.Done():
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Wrap(ctx.Err()), "role"))
return
case <-retry.After():
}
if tries >= r.p.MaxRetries() {
diagMessage := fmt.Sprintf("Error reading Role (tried %d times) - state outdated, please import resource", tries)
resp.Diagnostics.AddError(diagMessage, "role")
return
}
}
roleResource, ok := roleI.(*apitypes.RoleV6)
if !ok {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Errorf("Can not convert %T to RoleV6", roleI), "role"))
return
}
role = roleResource
diags = tfschema.CopyRoleV6ToTerraform(ctx, role, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Delete deletes Teleport Role
func (r resourceTeleportRole) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) {
var id types.String
diags := req.State.GetAttribute(ctx, path.Root("metadata").AtName("name"), &id)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
err := r.p.Client().DeleteRole(ctx, id.Value)
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting RoleV6", trace.Wrap(err), "role"))
return
}
resp.State.RemoveResource(ctx)
}
// ImportState imports Role state
func (r resourceTeleportRole) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) {
role, err := r.p.Client().GetRole(ctx, req.ID)
if err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Role", trace.Wrap(err), "role"))
return
}
roleResource := role.(*apitypes.RoleV6)
var state types.Object
diags := resp.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
diags = tfschema.CopyRoleV6ToTerraform(ctx, roleResource, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
id := roleResource.GetName()
state.Attrs["id"] = types.String{Value: id}
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// ModifyPlan modifies the planned value, normalizing null values.
func (r resourceTeleportRole) ModifyPlan(ctx context.Context, req tfsdk.ModifyResourcePlanRequest, resp *tfsdk.ModifyResourcePlanResponse) {
// If the entire plan is null, the resource is planned for destruction.
if req.Plan.Raw.IsNull() {
return
}
// If the state is null, the resource is being created. No need to modify plan.
if req.State.Raw.IsNull() {
return
}
var config types.Object
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}
role := &apitypes.RoleV6{}
resp.Diagnostics.Append(tfschema.CopyRoleV6FromTerraform(ctx, config, role)...)
if resp.Diagnostics.HasError() {
return
}
roleResource := role
if err := roleResource.CheckAndSetDefaults(); err != nil {
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting Role defaults", trace.Wrap(err), "role"))
return
}
role = roleResource
const preserveUnknown = true
resp.Diagnostics.Append(tfschema.CopyRoleV6ToTerraformPreserveUnknown(ctx, role, &config, preserveUnknown)...)
if resp.Diagnostics.HasError() {
return
}
var plan types.Object
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
plan.Attrs["spec"] = config.Attrs["spec"]
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
@@ -0,0 +1,65 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package resources
import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
apitypes "github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport"
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
"github.com/gravitational/teleport/integrations/terraform/tfschema"
)
// NewRoleDataSourceType returns the role data source type.
func NewRoleDataSourceType() tfdriver.DataSourceType[apitypes.RoleV6, tfdriver.NameIdentifier] {
return tfdriver.DataSourceType[apitypes.RoleV6, tfdriver.NameIdentifier]{
NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[apitypes.RoleV6, tfdriver.NameIdentifier] {
return teleport.NewRoleClient(clientFromProvider(p))
},
Kind: apitypes.KindRole,
Codec: tfdriver.DataSourceCodecFuncs[apitypes.RoleV6]{
SchemaFunc: tfschema.GenSchemaRoleV6,
ToStateFunc: tfschema.CopyRoleV6ToTerraform,
},
Identifier: tfdriver.NameIdentifierFromPath(path.Root("metadata").AtName("name")),
}
}
// NewRoleResourceType returns the role resource type.
func NewRoleResourceType() tfdriver.ResourceType[apitypes.RoleV6, tfdriver.NameIdentifier] {
return tfdriver.ResourceType[apitypes.RoleV6, tfdriver.NameIdentifier]{
NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[apitypes.RoleV6, tfdriver.NameIdentifier] {
return teleport.NewRoleClient(clientFromProvider(p))
},
Kind: apitypes.KindRole,
Codec: tfdriver.ResourceCodecFuncs[apitypes.RoleV6]{
SchemaFunc: tfschema.GenSchemaRoleV6,
ToStateFunc: tfschema.CopyRoleV6ToTerraform,
FromPlanFunc: tfschema.CopyRoleV6FromTerraform,
},
Normalizer: tfdriver.CheckAndSetDefaults[apitypes.RoleV6](),
Identifier: tfdriver.NameIdentifierPolicy(path.Root("metadata").AtName("name"), func(role *apitypes.RoleV6) string {
return role.GetMetadata().Name
}),
ResourceRevision: func(st *apitypes.RoleV6) string {
return st.GetMetadata().Revision
},
}
}
@@ -0,0 +1,76 @@
// Teleport
// Copyright (C) 2026 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package teleport
import (
"context"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
)
// NewRoleClient returns a role client.
func NewRoleClient(c *client.Client) RoleClient {
return RoleClient{client: c}
}
// RoleClient manages role resources.
type RoleClient struct {
client *client.Client
}
// Get reads a role by name.
func (r RoleClient) Get(ctx context.Context, id tfdriver.NameIdentifier) (*types.RoleV6, error) {
role, err := r.client.GetRole(ctx, id.Name)
if err != nil {
return nil, trace.Wrap(err)
}
rolev6, ok := role.(*types.RoleV6)
if !ok {
return nil, trace.BadParameter("unexpected role type: %T", role)
}
return rolev6, nil
}
// Create creates a role.
func (r RoleClient) Create(ctx context.Context, role *types.RoleV6) error {
if _, err := r.client.CreateRole(ctx, role); err != nil {
return trace.Wrap(err)
}
return nil
}
// Upsert updates a role.
func (r RoleClient) Upsert(ctx context.Context, role *types.RoleV6) error {
if _, err := r.client.UpsertRole(ctx, role); err != nil {
return trace.Wrap(err)
}
return nil
}
// Delete deletes a role by name.
func (r RoleClient) Delete(ctx context.Context, id tfdriver.NameIdentifier) error {
return trace.Wrap(r.client.DeleteRole(ctx, id.Name))
}
@@ -572,6 +572,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp
"teleport_installer": resources.NewInstallerResourceType(),
"teleport_kube_cluster": resources.NewKubernetesClusterResourceType(),
"teleport_lock": resources.NewLockResourceType(),
"teleport_role": resources.NewRoleResourceType(),
"teleport_saml_connector": resources.NewSAMLConnectorResourceType(),
"teleport_scoped_role": resources.NewScopedRoleResourceType(),
"teleport_static_host_user": resources.NewStaticHostUserResourceType(),
@@ -598,6 +599,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc
"teleport_installer": resources.NewInstallerDataSourceType(),
"teleport_kube_cluster": resources.NewKubernetesClusterDataSourceType(),
"teleport_lock": resources.NewLockDataSourceType(),
"teleport_role": resources.NewRoleDataSourceType(),
"teleport_saml_connector": resources.NewSAMLConnectorDataSourceType(),
"teleport_scoped_role": resources.NewScopedRoleDataSourceType(),
"teleport_static_host_user": resources.NewStaticHostUserDataSourceType(),