diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go
index 3ca7aae79f1..05d16876b69 100644
--- a/integrations/terraform/gen/main.go
+++ b/integrations/terraform/gen/main.go
@@ -261,22 +261,6 @@ const (
)
var (
- authPreference = payload{
- Name: "AuthPreference",
- TypeName: "AuthPreferenceV2",
- VarName: "authPreference",
- GetMethod: "GetAuthPreference",
- CreateMethod: "UpsertAuthPreference",
- UpdateMethod: "UpsertAuthPreference",
- UpsertMethodArity: 2,
- DeleteMethod: "ResetAuthPreference",
- ID: `"auth_preference"`,
- Kind: "cluster_auth_preference",
- HasStaticID: false,
- TerraformResourceType: "teleport_auth_preference",
- HasCheckAndSetDefaults: true,
- }
-
clusterMaintenance = payload{
Name: "ClusterMaintenanceConfig",
TypeName: "ClusterMaintenanceConfigV1",
@@ -1121,8 +1105,6 @@ func main() {
}
func genTFSchema() {
- generateResource(authPreference, singularResource)
- generateDataSource(authPreference, singularDataSource)
generateResource(clusterMaintenance, singularResource)
generateDataSource(clusterMaintenance, singularDataSource)
generateResource(clusterNetworking, singularResource)
diff --git a/integrations/terraform/provider/internal/legacy/data_source_teleport_auth_preference.go b/integrations/terraform/provider/internal/legacy/data_source_teleport_auth_preference.go
deleted file mode 100755
index 51287af6125..00000000000
--- a/integrations/terraform/provider/internal/legacy/data_source_teleport_auth_preference.go
+++ /dev/null
@@ -1,89 +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/tfsdk"
- "github.com/hashicorp/terraform-plugin-framework/types"
-
- "github.com/gravitational/teleport/integrations/terraform/tfschema"
- "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
-)
-
-// dataSourceTeleportAuthPreferenceType is the data source metadata type
-type dataSourceTeleportAuthPreferenceType struct{}
-
-// dataSourceTeleportAuthPreference is the resource
-type dataSourceTeleportAuthPreference struct {
- p Provider
-}
-
-// GetSchema returns the data source schema
-func (r dataSourceTeleportAuthPreferenceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
- return tfschema.GenSchemaAuthPreferenceV2(ctx)
-}
-
-// NewDataSource creates the empty data source
-func (r dataSourceTeleportAuthPreferenceType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) {
- return dataSourceTeleportAuthPreference{
- p: p.(Provider),
- }, nil
-}
-
-// Read reads teleport AuthPreference
-func (r dataSourceTeleportAuthPreference) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) {
- authPreferenceI, err := r.p.Client().GetAuthPreference(ctx)
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- var state types.Object
- resp.Diagnostics.Append(req.Config.Get(ctx, &state)...)
- if resp.Diagnostics.HasError() {
- return
- }
-
- authPreference := authPreferenceI.(*apitypes.AuthPreferenceV2)
- diags := tfschema.CopyAuthPreferenceV2ToTerraform(ctx, authPreference, &state)
- resp.Diagnostics.Append(diags...)
- 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() {
- id := authPreference.GetName()
- state.Attrs["id"] = types.String{Value: id}
- }
-
- diags = resp.State.Set(ctx, &state)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
- return
- }
-}
diff --git a/integrations/terraform/provider/internal/legacy/registry.go b/integrations/terraform/provider/internal/legacy/registry.go
index d25b1d22190..04c5ad5d22b 100644
--- a/integrations/terraform/provider/internal/legacy/registry.go
+++ b/integrations/terraform/provider/internal/legacy/registry.go
@@ -22,7 +22,6 @@ import "github.com/hashicorp/terraform-plugin-framework/tfsdk"
func DataSourceTypes() map[string]tfsdk.DataSourceType {
return map[string]tfsdk.DataSourceType{
"teleport_app_auth_config": dataSourceTeleportAppAuthConfigType{},
- "teleport_auth_preference": dataSourceTeleportAuthPreferenceType{},
"teleport_cluster_maintenance_config": dataSourceTeleportClusterMaintenanceConfigType{},
"teleport_cluster_networking_config": dataSourceTeleportClusterNetworkingConfigType{},
"teleport_database": dataSourceTeleportDatabaseType{},
@@ -68,7 +67,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType {
func ResourceTypes() map[string]tfsdk.ResourceType {
return map[string]tfsdk.ResourceType{
"teleport_app_auth_config": resourceTeleportAppAuthConfigType{},
- "teleport_auth_preference": resourceTeleportAuthPreferenceType{},
"teleport_cluster_maintenance_config": resourceTeleportClusterMaintenanceConfigType{},
"teleport_cluster_networking_config": resourceTeleportClusterNetworkingConfigType{},
"teleport_database": resourceTeleportDatabaseType{},
diff --git a/integrations/terraform/provider/internal/legacy/resource_teleport_auth_preference.go b/integrations/terraform/provider/internal/legacy/resource_teleport_auth_preference.go
deleted file mode 100755
index bd2c789f2bf..00000000000
--- a/integrations/terraform/provider/internal/legacy/resource_teleport_auth_preference.go
+++ /dev/null
@@ -1,384 +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/tfsdk"
- "github.com/hashicorp/terraform-plugin-framework/types"
-
- "github.com/gravitational/teleport/integrations/terraform/tfschema"
- "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
-)
-
-// resourceTeleportAuthPreferenceType is the resource metadata type
-type resourceTeleportAuthPreferenceType struct{}
-
-// resourceTeleportAuthPreference is the resource
-type resourceTeleportAuthPreference struct {
- p Provider
-}
-
-// GetSchema returns the resource schema
-func (r resourceTeleportAuthPreferenceType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
- return tfschema.GenSchemaAuthPreferenceV2(ctx)
-}
-
-// NewResource creates the empty resource
-func (r resourceTeleportAuthPreferenceType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) {
- return resourceTeleportAuthPreference{
- p: p.(Provider),
- }, nil
-}
-
-// Create creates the AuthPreference
-func (r resourceTeleportAuthPreference) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) {
- 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
- }
-
- authPreference := &apitypes.AuthPreferenceV2{}
- diags = tfschema.CopyAuthPreferenceV2FromTerraform(ctx, plan, authPreference)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
- return
- }
-
- err := authPreference.CheckAndSetDefaults()
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting AuthPreference defaults", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
-
-
- authPreferenceBefore, err := r.p.Client().GetAuthPreference(ctx)
- if err != nil && !trace.IsNotFound(err) {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- _, err = r.p.Client().UpsertAuthPreference(ctx, authPreference)
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- var authPreferenceI apitypes.AuthPreference
-
- // Try getting the resource until it exists and is different than the previous ones.
- // There are two types of singleton resources:
- // - the ones who can deleted and return a trace.NotFoundErr
- // - the ones who cannot be deleted, only reset. In this case, the resource revision is used to know if the change got applied.
- tries := 0
- retry, err := r.p.Retry()
- if err != nil {
- return
- }
- for {
- tries = tries + 1
- authPreferenceI, err = r.p.Client().GetAuthPreference(ctx)
- if trace.IsNotFound(err) {
- select {
- case <-ctx.Done():
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(ctx.Err()), "cluster_auth_preference"))
- return
- case <-retry.After():
- }
- if tries >= r.p.MaxRetries() {
- diagMessage := fmt.Sprintf("Error reading AuthPreference (tried %d times) - state outdated, please import resource", tries)
- resp.Diagnostics.AddError(diagMessage, "cluster_auth_preference")
- return
- }
- continue
- }
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- previousMetadata := authPreferenceBefore.GetMetadata()
- currentMetadata := authPreferenceI.GetMetadata()
- if previousMetadata.GetRevision() != currentMetadata.GetRevision() || false {
- break
- }
- select {
- case <-ctx.Done():
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(ctx.Err()), "cluster_auth_preference"))
- return
- case <-retry.After():
- }
- if tries >= r.p.MaxRetries() {
- diagMessage := fmt.Sprintf("Error reading AuthPreference (tried %d times) - state outdated, please import resource", tries)
- resp.Diagnostics.AddError(diagMessage, "cluster_auth_preference")
- return
- }
- }
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- authPreference, ok := authPreferenceI.(*apitypes.AuthPreferenceV2)
- if !ok {
- resp.Diagnostics.Append(
- tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Errorf("Can not convert %T to AuthPreferenceV2", authPreferenceI), "cluster_auth_preference"),
- )
- return
- }
-
- diags = tfschema.CopyAuthPreferenceV2ToTerraform(ctx, authPreference, &plan)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
- return
- }
-
- plan.Attrs["id"] = types.String{Value: "auth_preference"}
-
- diags = resp.State.Set(ctx, &plan)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
- return
- }
-}
-
-// Read reads teleport AuthPreference
-func (r resourceTeleportAuthPreference) 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
- }
-
- authPreferenceI, err := r.p.Client().GetAuthPreference(ctx)
- if trace.IsNotFound(err) {
- resp.State.RemoveResource(ctx)
- return
- }
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
-
- authPreference := authPreferenceI.(*apitypes.AuthPreferenceV2)
- diags = tfschema.CopyAuthPreferenceV2ToTerraform(ctx, authPreference, &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 AuthPreference
-func (r resourceTeleportAuthPreference) 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
- }
-
- authPreference := &apitypes.AuthPreferenceV2{}
- diags = tfschema.CopyAuthPreferenceV2FromTerraform(ctx, plan, authPreference)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
- return
- }
-
- err := authPreference.CheckAndSetDefaults()
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- authPreferenceBefore, err := r.p.Client().GetAuthPreference(ctx)
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- _, err = r.p.Client().UpsertAuthPreference(ctx, authPreference)
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
- var authPreferenceI apitypes.AuthPreference
-
- tries := 0
- retry, err := r.p.Retry()
- if err != nil {
- return
- }
- for {
- tries = tries + 1
- authPreferenceI, err = r.p.Client().GetAuthPreference(ctx)
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
- if authPreferenceBefore.GetMetadata().Revision != authPreferenceI.GetMetadata().Revision || false {
- break
- }
- select {
- case <-ctx.Done():
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(ctx.Err()), "cluster_auth_preference"))
- return
- case <-retry.After():
- }
- if tries >= r.p.MaxRetries() {
- diagMessage := fmt.Sprintf("Error reading AuthPreference (tried %d times) - state outdated, please import resource", tries)
- resp.Diagnostics.AddError(diagMessage, "cluster_auth_preference")
- return
- }
- }
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
-
- authPreference = authPreferenceI.(*apitypes.AuthPreferenceV2)
- diags = tfschema.CopyAuthPreferenceV2ToTerraform(ctx, authPreference, &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 AuthPreference
-func (r resourceTeleportAuthPreference) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) {
- err := r.p.Client().ResetAuthPreference(ctx)
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- resp.State.RemoveResource(ctx)
-}
-
-// ImportState imports AuthPreference state
-func (r resourceTeleportAuthPreference) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) {
- authPreferenceI, err := r.p.Client().GetAuthPreference(ctx)
- if err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AuthPreference", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
- authPreference := authPreferenceI.(*apitypes.AuthPreferenceV2)
-
- var state types.Object
-
- diags := resp.State.Get(ctx, &state)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
- return
- }
-
- diags = tfschema.CopyAuthPreferenceV2ToTerraform(ctx, authPreference, &state)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
- return
- }
- id := authPreference.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 resourceTeleportAuthPreference) 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
- }
-
- authPreference := &apitypes.AuthPreferenceV2{}
- resp.Diagnostics.Append(tfschema.CopyAuthPreferenceV2FromTerraform(ctx, config, authPreference)...)
- if resp.Diagnostics.HasError() {
- return
- }
-
- authPreferenceResource := authPreference
-
- if err := authPreferenceResource.CheckAndSetDefaults(); err != nil {
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting AuthPreference defaults", trace.Wrap(err), "cluster_auth_preference"))
- return
- }
-
- authPreference = authPreferenceResource
-
- const preserveUnknown = true
- resp.Diagnostics.Append(tfschema.CopyAuthPreferenceV2ToTerraformPreserveUnknown(ctx, authPreference, &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)...)
-}
diff --git a/integrations/terraform/provider/internal/resources/auth_preference.go b/integrations/terraform/provider/internal/resources/auth_preference.go
new file mode 100644
index 00000000000..ec4518236a2
--- /dev/null
+++ b/integrations/terraform/provider/internal/resources/auth_preference.go
@@ -0,0 +1,67 @@
+// 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 .
+
+package resources
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/tfsdk"
+
+ "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"
+)
+
+// authPreferenceID is the static identifier used in the terraform state.
+// Note that types.MetaNameClusterAuthPreference is "cluster-auth-preference"
+// which can't be used here without causing churn.
+const authPreferenceID = "auth_preference"
+
+// NewAuthPreferenceDataSourceType returns the cluster auth preference data source type.
+func NewAuthPreferenceDataSourceType() tfdriver.DataSourceType[types.AuthPreferenceV2, tfdriver.SingletonIdentifier] {
+ return tfdriver.DataSourceType[types.AuthPreferenceV2, tfdriver.SingletonIdentifier]{
+ NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[types.AuthPreferenceV2, tfdriver.SingletonIdentifier] {
+ return teleport.NewAuthPreferenceClient(clientFromProvider(p))
+ },
+ Kind: types.KindClusterAuthPreference,
+ Codec: tfdriver.DataSourceCodecFuncs[types.AuthPreferenceV2]{
+ SchemaFunc: tfschema.GenSchemaAuthPreferenceV2,
+ ToStateFunc: tfschema.CopyAuthPreferenceV2ToTerraform,
+ },
+ Identifier: tfdriver.SingletonIdentifierFromName(authPreferenceID),
+ }
+}
+
+// NewAuthPreferenceResourceType returns the cluster auth preference resource type.
+func NewAuthPreferenceResourceType() tfdriver.ResourceType[types.AuthPreferenceV2, tfdriver.SingletonIdentifier] {
+ return tfdriver.ResourceType[types.AuthPreferenceV2, tfdriver.SingletonIdentifier]{
+ NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[types.AuthPreferenceV2, tfdriver.SingletonIdentifier] {
+ return teleport.NewAuthPreferenceClient(clientFromProvider(p))
+ },
+ Kind: types.KindClusterAuthPreference,
+ Codec: tfdriver.ResourceCodecFuncs[types.AuthPreferenceV2]{
+ SchemaFunc: tfschema.GenSchemaAuthPreferenceV2,
+ ToStateFunc: tfschema.CopyAuthPreferenceV2ToTerraform,
+ FromPlanFunc: tfschema.CopyAuthPreferenceV2FromTerraform,
+ },
+ Normalizer: tfdriver.CheckAndSetDefaults[types.AuthPreferenceV2](),
+ Identifier: tfdriver.SingletonIdentifierPolicy[types.AuthPreferenceV2](authPreferenceID),
+ ResourceRevision: func(st *types.AuthPreferenceV2) string {
+ return st.GetMetadata().Revision
+ },
+ }
+}
diff --git a/integrations/terraform/provider/internal/teleport/auth_preference.go b/integrations/terraform/provider/internal/teleport/auth_preference.go
new file mode 100644
index 00000000000..daef400cde5
--- /dev/null
+++ b/integrations/terraform/provider/internal/teleport/auth_preference.go
@@ -0,0 +1,72 @@
+// 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 .
+
+package teleport
+
+import (
+ "context"
+
+ "github.com/gravitational/trace"
+
+ "github.com/gravitational/teleport/api/client"
+ clusterconfigv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1"
+ "github.com/gravitational/teleport/api/types"
+
+ "github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
+)
+
+// NewAuthPreferenceClient returns a Teleport client to interact with the cluster auth preference.
+func NewAuthPreferenceClient(c *client.Client) AuthPreferenceClient {
+ return AuthPreferenceClient{client: c}
+}
+
+// AuthPreferenceClient manages the Teleport cluster auth preference..
+type AuthPreferenceClient struct {
+ client *client.Client
+}
+
+// Get reads a the Teleport clust auth preference.
+func (r AuthPreferenceClient) Get(ctx context.Context, _ tfdriver.SingletonIdentifier) (*types.AuthPreferenceV2, error) {
+ pref, err := r.client.ClusterConfigClient().GetAuthPreference(ctx, &clusterconfigv1.GetAuthPreferenceRequest{})
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ return pref, nil
+}
+
+// Create creates the Teleport cluster auth preference.
+func (r AuthPreferenceClient) Create(ctx context.Context, pref *types.AuthPreferenceV2) error {
+ if _, err := r.client.UpsertAuthPreference(ctx, pref); err != nil {
+ return trace.Wrap(err)
+ }
+
+ return nil
+}
+
+// Upsert updates the Teleport cluster auth preference.
+func (r AuthPreferenceClient) Upsert(ctx context.Context, pref *types.AuthPreferenceV2) error {
+ if _, err := r.client.UpsertAuthPreference(ctx, pref); err != nil {
+ return trace.Wrap(err)
+ }
+
+ return nil
+}
+
+// Delete resets the Teleport cluster auth preference.
+func (r AuthPreferenceClient) Delete(ctx context.Context, _ tfdriver.SingletonIdentifier) error {
+ return trace.Wrap(r.client.ResetAuthPreference(ctx))
+}
diff --git a/integrations/terraform/provider/internal/tfdriver/resource.go b/integrations/terraform/provider/internal/tfdriver/resource.go
index 937b6e2453d..b3f74883854 100644
--- a/integrations/terraform/provider/internal/tfdriver/resource.go
+++ b/integrations/terraform/provider/internal/tfdriver/resource.go
@@ -130,19 +130,21 @@ func (r Resource[T, I]) Create(ctx context.Context, req tfsdk.CreateResourceRequ
}
id := r.resource.Identifier.FromResource(val)
- _, err := r.resourceClient.Get(ctx, id)
- if !trace.IsNotFound(err) {
- if err == nil {
- resp.Diagnostics.Append(
- tfdiag.DiagFromErr(
- fmt.Sprintf("%q exists in Teleport", r.resource.Kind),
- trace.Errorf("%[1]s exists in Teleport. Either remove it (tctl rm %[1]s/%[2]v)"+
- " or import it to the existing state (terraform import teleport_%[1]s.%[2]v %[2]v)", r.resource.Kind, id),
- ),
- )
- return
- }
+ _, singletonResource := any(id).(SingletonIdentifier)
+ resourceBefore, err := r.resourceClient.Get(ctx, id)
+ switch {
+ case trace.IsNotFound(err):
+ case err == nil && !singletonResource:
+ resp.Diagnostics.Append(
+ tfdiag.DiagFromErr(
+ fmt.Sprintf("%q exists in Teleport", r.resource.Kind),
+ trace.Errorf("%[1]s exists in Teleport. Either remove it (tctl rm %[1]s/%[2]v)"+
+ " or import it to the existing state (terraform import teleport_%[1]s.%[2]v %[2]v)", r.resource.Kind, id),
+ ),
+ )
+ return
+ case err != nil:
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr(fmt.Sprintf("Error reading %q", r.resource.Kind), trace.Wrap(err), r.resource.Kind))
return
}
@@ -159,42 +161,41 @@ func (r Resource[T, I]) Create(ctx context.Context, req tfsdk.CreateResourceRequ
for tries := 1; ; tries++ {
retrieved, err := r.resourceClient.Get(ctx, id)
- if trace.IsNotFound(err) {
- if tries >= r.runtime.MaxRetries() {
- diagMessage := fmt.Sprintf("Error reading %q (tried %d times) - state outdated, please import resource", r.resource.Kind, tries)
- resp.Diagnostics.AddError(diagMessage, r.resource.Kind)
- return
- }
-
- select {
- case <-ctx.Done():
- resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr(fmt.Sprintf("Error reading %q", r.resource.Kind), trace.Wrap(ctx.Err()), r.resource.Kind))
- return
- case <-retry.After():
- }
-
- continue
- }
- if err != nil {
+ switch {
+ case trace.IsNotFound(err):
+ case err != nil:
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr(fmt.Sprintf("Error reading %q", r.resource.Kind), trace.Wrap(err), r.resource.Kind))
return
- }
+ case singletonResource &&
+ resourceBefore != nil &&
+ r.resource.ResourceRevision(resourceBefore) == r.resource.ResourceRevision(retrieved):
+ default:
+ diags = r.resource.Codec.ToState(ctx, retrieved, &plan)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ plan.Attrs["id"] = types.String{Value: id.String()}
+
+ diags = resp.State.Set(ctx, &plan)
+ resp.Diagnostics.Append(diags...)
- diags = r.resource.Codec.ToState(ctx, retrieved, &plan)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
return
}
- plan.Attrs["id"] = types.String{Value: id.String()}
-
- diags = resp.State.Set(ctx, &plan)
- resp.Diagnostics.Append(diags...)
- if resp.Diagnostics.HasError() {
+ if tries >= r.runtime.MaxRetries() {
+ diagMessage := fmt.Sprintf("Error reading %q (tried %d times) - state outdated, please import resource", r.resource.Kind, tries)
+ resp.Diagnostics.AddError(diagMessage, r.resource.Kind)
return
}
- return
+ select {
+ case <-ctx.Done():
+ resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr(fmt.Sprintf("Error reading %q", r.resource.Kind), trace.Wrap(ctx.Err()), r.resource.Kind))
+ return
+ case <-retry.After():
+ }
}
}
diff --git a/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go
index 0481b12beae..317d8e3f1bf 100644
--- a/integrations/terraform/provider/provider.go
+++ b/integrations/terraform/provider/provider.go
@@ -567,6 +567,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp
"teleport_access_list": resources.NewAccessListResourceType(),
"teleport_access_list_member": resources.NewAccessListMemberResourceType(),
"teleport_app": resources.NewAppResourceType(),
+ "teleport_auth_preference": resources.NewAuthPreferenceResourceType(),
"teleport_kube_cluster": resources.NewKubernetesClusterResourceType(),
"teleport_saml_connector": resources.NewSAMLConnectorResourceType(),
"teleport_scoped_role": resources.NewScopedRoleResourceType(),
@@ -587,6 +588,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc
"teleport_access_list": resources.NewAccessListDataSourceType(),
"teleport_access_list_member": resources.NewAccessListMemberDataSourceType(),
"teleport_app": resources.NewAppDataSourceType(),
+ "teleport_auth_preference": resources.NewAuthPreferenceDataSourceType(),
"teleport_kube_cluster": resources.NewKubernetesClusterDataSourceType(),
"teleport_saml_connector": resources.NewSAMLConnectorDataSourceType(),
"teleport_scoped_role": resources.NewScopedRoleDataSourceType(),
diff --git a/integrations/terraform/testlib/auth_preference_test.go b/integrations/terraform/testlib/auth_preference_test.go
index 206456cab26..e02ae64d49d 100644
--- a/integrations/terraform/testlib/auth_preference_test.go
+++ b/integrations/terraform/testlib/auth_preference_test.go
@@ -17,7 +17,6 @@ limitations under the License.
package testlib
import (
- "context"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
@@ -80,12 +79,8 @@ func (s *TerraformSuiteOSS) TestAuthPreference() {
}
func (s *TerraformSuiteOSS) TestImportAuthPreference() {
- ctx, cancel := context.WithCancel(context.Background())
- s.T().Cleanup(cancel)
-
r := "teleport_auth_preference"
- id := "test_import"
- name := r + "." + id
+ name := r + "." + "auth_preference"
authPreference := &types.AuthPreferenceV2{
Metadata: types.Metadata{},
@@ -96,14 +91,14 @@ func (s *TerraformSuiteOSS) TestImportAuthPreference() {
err := authPreference.CheckAndSetDefaults()
require.NoError(s.T(), err)
- authPreferencesBefore, err := s.client.GetAuthPreference(ctx)
+ authPreferencesBefore, err := s.client.GetAuthPreference(s.T().Context())
require.NoError(s.T(), err)
- _, err = s.client.ClusterConfigClient().UpsertAuthPreference(ctx, &clusterconfigv1.UpsertAuthPreferenceRequest{AuthPreference: authPreference})
+ _, err = s.client.ClusterConfigClient().UpsertAuthPreference(s.T().Context(), &clusterconfigv1.UpsertAuthPreferenceRequest{AuthPreference: authPreference})
require.NoError(s.T(), err)
require.Eventually(s.T(), func() bool {
- authPreferencesCurrent, err := s.client.GetAuthPreference(ctx)
+ authPreferencesCurrent, err := s.client.GetAuthPreference(s.T().Context())
require.NoError(s.T(), err)
return authPreferencesBefore.GetMetadata().Revision != authPreferencesCurrent.GetMetadata().Revision
@@ -114,10 +109,10 @@ func (s *TerraformSuiteOSS) TestImportAuthPreference() {
IsUnitTest: true,
Steps: []resource.TestStep{
{
- Config: s.terraformConfig + "\n" + `resource "` + r + `" "` + id + `" { }`,
+ Config: s.terraformConfig + "\n" + `resource "` + r + `" "auth_preference" { }`,
ResourceName: name,
ImportState: true,
- ImportStateId: id,
+ ImportStateId: "auth_preference",
ImportStateCheck: func(state []*terraform.InstanceState) error {
require.Equal(s.T(), "cluster_auth_preference", state[0].Attributes["kind"])
require.Equal(s.T(), "true", state[0].Attributes["spec.disconnect_expired_cert"])