Terraform: Convert Auth Preference to generic provider (#69099)

This commit is contained in:
rosstimothy
2026-08-07 13:19:03 +00:00
committed by GitHub
parent 0a47b8dacd
commit ebb728fbd2
9 changed files with 187 additions and 543 deletions
-18
View File
@@ -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)
@@ -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
}
}
@@ -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{},
@@ -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)...)
}
@@ -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 <http://www.gnu.org/licenses/>.
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
},
}
}
@@ -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 <http://www.gnu.org/licenses/>.
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))
}
@@ -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():
}
}
}
@@ -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(),
@@ -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"])