From e5848d640a310edbcaffc242817f6a94ac3f23bc Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Mon, 5 Feb 2024 14:10:01 -0500 Subject: [PATCH 01/11] add terraform support for Teleport servers --- terraform/example/server.tf.example | 11 + terraform/gen/main.go | 28 + terraform/gen/plural_data_source.go.tpl | 5 +- terraform/gen/plural_resource.go.tpl | 21 +- terraform/protoc-gen-terraform-teleport.yaml | 26 + .../provider/data_source_teleport_server.go | 83 + terraform/provider/provider.go | 1 + .../provider/resource_teleport_server.go | 335 +++ terraform/reference.mdx | 102 + .../test/fixtures/server_openssh_0_create.tf | 11 + .../test/fixtures/server_openssh_1_update.tf | 11 + terraform/test/main_test.go | 2 + terraform/test/server_test.go | 72 + terraform/tfschema/types_terraform.go | 1895 +++++++++++++++++ terraform/tfschema/validators.go | 46 + 15 files changed, 2641 insertions(+), 8 deletions(-) create mode 100644 terraform/example/server.tf.example create mode 100755 terraform/provider/data_source_teleport_server.go create mode 100755 terraform/provider/resource_teleport_server.go create mode 100644 terraform/test/fixtures/server_openssh_0_create.tf create mode 100644 terraform/test/fixtures/server_openssh_1_update.tf create mode 100644 terraform/test/server_test.go diff --git a/terraform/example/server.tf.example b/terraform/example/server.tf.example new file mode 100644 index 000000000..b908d0ad0 --- /dev/null +++ b/terraform/example/server.tf.example @@ -0,0 +1,11 @@ +resource "teleport_server" "ssh_agentless" { + version = "v2" + sub_kind = "openssh" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + } +} diff --git a/terraform/gen/main.go b/terraform/gen/main.go index 16fec926c..0539687f8 100644 --- a/terraform/gen/main.go +++ b/terraform/gen/main.go @@ -108,6 +108,13 @@ type payload struct { // existing resource when we're updating it. For example: // "Spec.Audit.NextAuditDate" in AccessList resource PropagatedFields []string + // Namespaced indicates that the resource get and delete methods need the + // deprecated namespace parameter (always the default namespace). + Namespaced bool + // ForceSetKind indicates that the resource kind must be forcefully set by the provider. + // This is required for weird resources (ServerV2) that support multiple kinds. + // For those resource, we must set the kind, and don't want to have the user do it. + ForceSetKind string } func (p *payload) CheckAndSetDefaults() error { @@ -424,6 +431,24 @@ var ( HasCheckAndSetDefaults: true, PropagatedFields: []string{"Spec.Audit.NextAuditDate"}, } + + server = payload{ + Name: "Server", + TypeName: "ServerV2", + VarName: "server", + GetMethod: "GetNode", + CreateMethod: "UpsertNode", + UpdateMethod: "UpsertNode", + UpsertMethodArity: 2, + DeleteMethod: "DeleteNode", + ID: "server.Metadata.Name", + Kind: "node", + HasStaticID: false, + TerraformResourceType: "teleport_server", + HasCheckAndSetDefaults: true, + Namespaced: true, + ForceSetKind: "apitypes.KindNode", + } ) func main() { @@ -469,6 +494,8 @@ func genTFSchema() { generateDataSource(oktaImportRule, pluralDataSource) generateResource(accessList, pluralResource) generateDataSource(accessList, pluralDataSource) + generateResource(server, pluralResource) + generateDataSource(server, pluralDataSource) } func generateResource(p payload, tpl string) { @@ -540,6 +567,7 @@ var ( "session_recording_config": tfschema.GenSchemaSessionRecordingConfigV2, "trusted_cluster": tfschema.GenSchemaTrustedClusterV2, "user": tfschema.GenSchemaUserV2, + "server": tfschema.GenSchemaServerV2, } // hiddenFields are fields that are not outputted to the reference doc. diff --git a/terraform/gen/plural_data_source.go.tpl b/terraform/gen/plural_data_source.go.tpl index 1a62f4c8c..afc79d585 100644 --- a/terraform/gen/plural_data_source.go.tpl +++ b/terraform/gen/plural_data_source.go.tpl @@ -28,6 +28,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + {{- if .Namespaced }} + "github.com/gravitational/teleport/api/defaults" + {{- end }} {{ schemaImport . }} ) @@ -65,7 +68,7 @@ func (r dataSourceTeleport{{.Name}}) Read(ctx context.Context, req tfsdk.ReadDat return } - {{.VarName}}I, err := r.p.Client.{{.GetMethod}}(ctx, id.Value{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) + {{.VarName}}I, err := r.p.Client.{{.GetMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}id.Value{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error reading {{.Name}}", trace.Wrap(err), "{{.Kind}}")) return diff --git a/terraform/gen/plural_resource.go.tpl b/terraform/gen/plural_resource.go.tpl index c0ed4d9e9..1dcb78bdf 100644 --- a/terraform/gen/plural_resource.go.tpl +++ b/terraform/gen/plural_resource.go.tpl @@ -41,6 +41,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/jonboulle/clockwork" +{{- if .Namespaced }} + "github.com/gravitational/teleport/api/defaults" +{{- end }} {{ schemaImport . }} ) @@ -118,7 +121,7 @@ func (r resourceTeleport{{.Name}}) Create(ctx context.Context, req tfsdk.CreateR {{end}} id := {{.VarName}}Resource.Metadata.Name - _, err = r.p.Client.{{.GetMethod}}(ctx, id{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) + _, err = r.p.Client.{{.GetMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}id{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) if !trace.IsNotFound(err) { if err == nil { existErr := fmt.Sprintf("{{.Name}} exists in Teleport. Either remove it (tctl rm {{.Kind}}/%v)"+ @@ -132,6 +135,10 @@ func (r resourceTeleport{{.Name}}) Create(ctx context.Context, req tfsdk.CreateR return } + {{- if .ForceSetKind }} + {{.VarName}}Resource.Kind = {{.ForceSetKind}} + {{- end}} + {{if .HasCheckAndSetDefaults -}} err = {{.VarName}}Resource.CheckAndSetDefaults() if err != nil { @@ -159,7 +166,7 @@ func (r resourceTeleport{{.Name}}) Create(ctx context.Context, req tfsdk.CreateR backoff := backoff.NewDecorr(r.p.RetryConfig.Base, r.p.RetryConfig.Cap, clockwork.NewRealClock()) for { tries = tries + 1 - {{.VarName}}I, err = r.p.Client.{{.GetMethod}}(ctx, id{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) + {{.VarName}}I, err = r.p.Client.{{.GetMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}id{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) if trace.IsNotFound(err) { if bErr := backoff.Do(ctx); bErr != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error reading {{.Name}}", trace.Wrap(err), "{{.Kind}}")) @@ -231,7 +238,7 @@ func (r resourceTeleport{{.Name}}) Read(ctx context.Context, req tfsdk.ReadResou return } - {{.VarName}}I, err := r.p.Client.{{.GetMethod}}(ctx, id.Value{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) + {{.VarName}}I, err := r.p.Client.{{.GetMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}id.Value{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) if trace.IsNotFound(err) { resp.State.RemoveResource(ctx) return @@ -300,7 +307,7 @@ func (r resourceTeleport{{.Name}}) Update(ctx context.Context, req tfsdk.UpdateR {{- end}} name := {{.VarName}}Resource.Metadata.Name - {{.VarName}}Before, err := r.p.Client.{{.GetMethod}}(ctx, name{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) + {{.VarName}}Before, err := r.p.Client.{{.GetMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}name{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error reading {{.Name}}", err, "{{.Kind}}")) return @@ -331,7 +338,7 @@ func (r resourceTeleport{{.Name}}) Update(ctx context.Context, req tfsdk.UpdateR backoff := backoff.NewDecorr(r.p.RetryConfig.Base, r.p.RetryConfig.Cap, clockwork.NewRealClock()) for { tries = tries + 1 - {{.VarName}}I, err = r.p.Client.{{.GetMethod}}(ctx, name{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) + {{.VarName}}I, err = r.p.Client.{{.GetMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}name{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error reading {{.Name}}", err, "{{.Kind}}")) return @@ -386,7 +393,7 @@ func (r resourceTeleport{{.Name}}) Delete(ctx context.Context, req tfsdk.DeleteR return } - err := r.p.Client.{{.DeleteMethod}}(ctx, id.Value) + err := r.p.Client.{{.DeleteMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}id.Value) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error deleting {{.TypeName}}", trace.Wrap(err), "{{.Kind}}")) return @@ -397,7 +404,7 @@ func (r resourceTeleport{{.Name}}) Delete(ctx context.Context, req tfsdk.DeleteR // ImportState imports {{.Name}} state func (r resourceTeleport{{.Name}}) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { - {{.VarName}}, err := r.p.Client.{{.GetMethod}}(ctx, req.ID{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) + {{.VarName}}, err := r.p.Client.{{.GetMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}req.ID{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error reading {{.Name}}", trace.Wrap(err), "{{.Kind}}")) return diff --git a/terraform/protoc-gen-terraform-teleport.yaml b/terraform/protoc-gen-terraform-teleport.yaml index 1bd336309..403dd5a65 100644 --- a/terraform/protoc-gen-terraform-teleport.yaml +++ b/terraform/protoc-gen-terraform-teleport.yaml @@ -20,6 +20,7 @@ types: - "SessionRecordingConfigV2" - "TrustedClusterV2" - "UserV2" + - "ServerV2" # id field is required for integration tests. It is not used by provider. # We have to add it manually (might be removed in the future versions). @@ -122,6 +123,13 @@ injected_fields: computed: true plan_modifiers: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" + ServerV2: + - + name: id + type: github.com/hashicorp/terraform-plugin-framework/types.StringType + computed: true + plan_modifiers: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" # These fields will be excluded exclude_fields: @@ -153,6 +161,9 @@ exclude_fields: - "UserSpecV2.CreatedBy" - "UserSpecV2.Status" + # Server + - "ServerSpecV2.CmdLabels" + name_overrides: # These fields will be marked as Computed: true @@ -251,6 +262,9 @@ computed_fields: # User - "UserV2.Kind" + # Server + - "ServerV2.Kind" + # These fields will be marked as Required: true required_fields: # App @@ -310,6 +324,11 @@ required_fields: - "UserV2.Metadata.Name" - "UserV2.Version" + # Server + - "ServerV2.Metadata.Name" + - "ServerV2.Version" + - "ServerV2.SubKind" + - "SessionRecordingConfigV2.Version" - "ClusterMaintenanceConfigV1.Version" - "AuthPreferenceV2.Version" @@ -385,6 +404,9 @@ plan_modifiers: AuthPreferenceV2.Version: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + ServerV2.Version: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" validators: # Expires must be in the future @@ -426,6 +448,10 @@ validators: - UseVersionBetween(2,2) ClusterMaintenanceConfigV1.Version: - UseVersionBetween(1,1) + ServerV2.Version: + - UseVersionBetween(2,2) + ServerV2.SubKind: + - UseValueIn([]string{"openssh", "openssh-ec2-ice"}) time_type: type: "TimeType" diff --git a/terraform/provider/data_source_teleport_server.go b/terraform/provider/data_source_teleport_server.go new file mode 100755 index 000000000..09813bf0c --- /dev/null +++ b/terraform/provider/data_source_teleport_server.go @@ -0,0 +1,83 @@ +// Code generated by _gen/main.go DO NOT EDIT +/* +Copyright 2015-2022 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 provider + +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/api/defaults" + + "github.com/gravitational/teleport-plugins/terraform/tfschema" +) + +// dataSourceTeleportServerType is the data source metadata type +type dataSourceTeleportServerType struct{} + +// dataSourceTeleportServer is the resource +type dataSourceTeleportServer struct { + p Provider +} + +// GetSchema returns the data source schema +func (r dataSourceTeleportServerType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfschema.GenSchemaServerV2(ctx) +} + +// NewDataSource creates the empty data source +func (r dataSourceTeleportServerType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { + return dataSourceTeleportServer{ + p: *(p.(*Provider)), + }, nil +} + +// Read reads teleport Server +func (r dataSourceTeleportServer) 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 + } + + serverI, err := r.p.Client.GetNode(ctx, defaults.Namespace, id.Value) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) + return + } + + var state types.Object + + server := serverI.(*apitypes.ServerV2) + diags = tfschema.CopyServerV2ToTerraform(ctx, server, &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 + } +} diff --git a/terraform/provider/provider.go b/terraform/provider/provider.go index 2d41fbcde..e9eb81b29 100644 --- a/terraform/provider/provider.go +++ b/terraform/provider/provider.go @@ -581,6 +581,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_trusted_device": resourceTeleportDeviceV1Type{}, "teleport_okta_import_rule": resourceTeleportOktaImportRuleType{}, "teleport_access_list": resourceTeleportAccessListType{}, + "teleport_server": resourceTeleportServerType{}, }, nil } diff --git a/terraform/provider/resource_teleport_server.go b/terraform/provider/resource_teleport_server.go new file mode 100755 index 000000000..f39632683 --- /dev/null +++ b/terraform/provider/resource_teleport_server.go @@ -0,0 +1,335 @@ +// Code generated by _gen/main.go DO NOT EDIT +/* +Copyright 2015-2022 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 provider + +import ( + "context" + "fmt" + + apitypes "github.com/gravitational/teleport/api/types" + + "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/integrations/lib/backoff" + "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/jonboulle/clockwork" + + "github.com/gravitational/teleport-plugins/terraform/tfschema" +) + +// resourceTeleportServerType is the resource metadata type +type resourceTeleportServerType struct{} + +// resourceTeleportServer is the resource +type resourceTeleportServer struct { + p Provider +} + +// GetSchema returns the resource schema +func (r resourceTeleportServerType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return tfschema.GenSchemaServerV2(ctx) +} + +// NewResource creates the empty resource +func (r resourceTeleportServerType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceTeleportServer{ + p: *(p.(*Provider)), + }, nil +} + +// Create creates the Server +func (r resourceTeleportServer) 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 + } + + server := &apitypes.ServerV2{} + diags = tfschema.CopyServerV2FromTerraform(ctx, plan, server) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + serverResource := server + + id := serverResource.Metadata.Name + + _, err = r.p.Client.GetNode(ctx, defaults.Namespace, id) + if !trace.IsNotFound(err) { + if err == nil { + existErr := fmt.Sprintf("Server exists in Teleport. Either remove it (tctl rm node/%v)"+ + " or import it to the existing state (terraform import teleport_server.%v %v)", id, id, id) + + resp.Diagnostics.Append(diagFromErr("Server exists in Teleport", trace.Errorf(existErr))) + return + } + + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) + return + } + serverResource.Kind = apitypes.KindNode + + err = serverResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting Server defaults", trace.Wrap(err), "node")) + return + } + + _, err = r.p.Client.UpsertNode(ctx, serverResource) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error creating Server", trace.Wrap(err), "node")) + return + } + + // Not really an inferface, just using the same name for easier templating. + var serverI apitypes.Server + tries := 0 + backoff := backoff.NewDecorr(r.p.RetryConfig.Base, r.p.RetryConfig.Cap, clockwork.NewRealClock()) + for { + tries = tries + 1 + serverI, err = r.p.Client.GetNode(ctx, defaults.Namespace, id) + if trace.IsNotFound(err) { + if bErr := backoff.Do(ctx); bErr != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) + return + } + if tries >= r.p.RetryConfig.MaxTries { + diagMessage := fmt.Sprintf("Error reading Server (tried %d times) - state outdated, please import resource", tries) + resp.Diagnostics.Append(diagFromWrappedErr(diagMessage, trace.Wrap(err), "node")) + return + } + continue + } + break + } + + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) + return + } + + serverResource, ok := serverI.(*apitypes.ServerV2) + if !ok { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Errorf("Can not convert %T to ServerV2", serverI), "node")) + return + } + server = serverResource + + diags = tfschema.CopyServerV2ToTerraform(ctx, server, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + plan.Attrs["id"] = types.String{Value: server.Metadata.Name} + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read reads teleport Server +func (r resourceTeleportServer) 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 + } + + serverI, err := r.p.Client.GetNode(ctx, defaults.Namespace, id.Value) + if trace.IsNotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) + return + } + + server := serverI.(*apitypes.ServerV2) + diags = tfschema.CopyServerV2ToTerraform(ctx, server, &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 Server +func (r resourceTeleportServer) 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 + } + + server := &apitypes.ServerV2{} + diags = tfschema.CopyServerV2FromTerraform(ctx, plan, server) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + serverResource := server + + if err := serverResource.CheckAndSetDefaults(); err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error updating Server", err, "node")) + return + } + name := serverResource.Metadata.Name + + serverBefore, err := r.p.Client.GetNode(ctx, defaults.Namespace, name) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", err, "node")) + return + } + + _, err = r.p.Client.UpsertNode(ctx, serverResource) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error updating Server", err, "node")) + return + } + + // Not really an inferface, just using the same name for easier templating. + var serverI apitypes.Server + + tries := 0 + backoff := backoff.NewDecorr(r.p.RetryConfig.Base, r.p.RetryConfig.Cap, clockwork.NewRealClock()) + for { + tries = tries + 1 + serverI, err = r.p.Client.GetNode(ctx, defaults.Namespace, name) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", err, "node")) + return + } + if serverBefore.GetMetadata().ID != serverI.GetMetadata().ID || false { + break + } + + if err := backoff.Do(ctx); err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) + return + } + if tries >= r.p.RetryConfig.MaxTries { + diagMessage := fmt.Sprintf("Error reading Server (tried %d times) - state outdated, please import resource", tries) + resp.Diagnostics.AddError(diagMessage, "node") + return + } + } + + serverResource, ok := serverI.(*apitypes.ServerV2) + if !ok { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Errorf("Can not convert %T to ServerV2", serverI), "node")) + return + } + diags = tfschema.CopyServerV2ToTerraform(ctx, server, &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 Server +func (r resourceTeleportServer) 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.DeleteNode(ctx, defaults.Namespace, id.Value) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error deleting ServerV2", trace.Wrap(err), "node")) + return + } + + resp.State.RemoveResource(ctx) +} + +// ImportState imports Server state +func (r resourceTeleportServer) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + server, err := r.p.Client.GetNode(ctx, defaults.Namespace, req.ID) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) + return + } + + serverResource := server.(*apitypes.ServerV2) + + var state types.Object + + diags := resp.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = tfschema.CopyServerV2ToTerraform(ctx, serverResource, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + id := serverResource.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/terraform/reference.mdx b/terraform/reference.mdx index 0901b1a1f..944ec5e7c 100755 --- a/terraform/reference.mdx +++ b/terraform/reference.mdx @@ -21,6 +21,7 @@ Supported resources: - [teleport_provision_token](#teleport_provision_token) - [teleport_role](#teleport_role) - [teleport_saml_connector](#teleport_saml_connector) +- [teleport_server](#teleport_server) - [teleport_session_recording_config](#teleport_session_recording_config) - [teleport_trusted_cluster](#teleport_trusted_cluster) - [teleport_trusted_device](#teleport_trusted_device) @@ -2189,6 +2190,107 @@ resource "teleport_saml_connector" "example" { } ``` +## teleport_server + +| Name | Type | Required | Description | +|----------|--------|----------|------------------------------------------------------------------| +| metadata | object | | Metadata is resource metadata | +| spec | object | | Spec is a server spec | +| sub_kind | string | * | SubKind is an optional resource sub kind, used in some resources | +| version | string | * | Version is version | + +### metadata + +Metadata is resource metadata + +| Name | Type | Required | Description | +|-------------|----------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| description | string | | Description is object description | +| expires | RFC3339 time | | Expires is a global expiry time header can be set on any resource in the system. | +| labels | map of strings | | Labels is a set of labels | +| name | string | * | Name is an object name | +| namespace | string | | Namespace is object namespace. The field should be called "namespace" when it returns in Teleport 2.4. | +| revision | string | | Revision is an opaque identifier which tracks the versions of a resource over time. Clients should ignore and not alter its value but must return the revision in any updates of a resource. | + +### spec + +Spec is a server spec + +| Name | Type | Required | Description | +|----------------|------------------|----------|-----------------------------------------------------------------------------------------| +| addr | string | | Addr is a host:port address where this server can be reached. | +| cloud_metadata | object | | CloudMetadata contains info about the cloud instance the server is running on, if any. | +| hostname | string | | Hostname is server hostname | +| peer_addr | string | | PeerAddr is the address a proxy server is reachable at by its peer proxies. | +| proxy_ids | array of strings | | ProxyIDs is a list of proxy IDs this server is expected to be connected to. | +| public_addrs | array of strings | | PublicAddrs is a list of public addresses where this server can be reached. | +| rotation | object | | Rotation specifies server rotation | +| use_tunnel | bool | | UseTunnel indicates that connections to this server should occur over a reverse tunnel. | +| version | string | | TeleportVersion is the teleport version that the server is running on | + +#### spec.cloud_metadata + +CloudMetadata contains info about the cloud instance the server is running on, if any. + +| Name | Type | Required | Description | +|------|--------|----------|----------------------------------------------------------| +| aws | object | | AWSInfo contains attributes to match to an EC2 instance. | + +##### spec.cloud_metadata.aws + +AWSInfo contains attributes to match to an EC2 instance. + +| Name | Type | Required | Description | +|-------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| account_id | string | | AccountID is an AWS account ID. | +| instance_id | string | | InstanceID is an EC2 instance ID. | +| integration | string | | Integration is the integration name that added this Node. When connecting to it, it will use this integration to issue AWS API calls in order to set up the connection. This includes sending an SSH Key and then opening a tunnel (EC2 Instance Connect Endpoint) so Teleport can connect to it. | +| region | string | | Region is the AWS EC2 Instance Region. | +| subnet_id | string | | SubnetID is the Subnet ID in use by the instance. | +| vpc_id | string | | VPCID is the AWS VPC ID where the Instance is running. | + +#### spec.rotation + +Rotation specifies server rotation + +| Name | Type | Required | Description | +|--------------|--------------|----------|-------------------------------------------------------------------------------------------------------------------------------| +| current_id | string | | CurrentID is the ID of the rotation operation to differentiate between rotation attempts. | +| grace_period | duration | | GracePeriod is a period during which old and new CA are valid for checking purposes, but only new CA is issuing certificates. | +| last_rotated | RFC3339 time | | LastRotated specifies the last time of the completed rotation. | +| mode | string | | Mode sets manual or automatic rotation mode. | +| phase | string | | Phase is the current rotation phase. | +| schedule | object | | Schedule is a rotation schedule - used in automatic mode to switch between phases. | +| started | RFC3339 time | | Started is set to the time when rotation has been started in case if the state of the rotation is "in_progress". | +| state | string | | State could be one of "init" or "in_progress". | + +##### spec.rotation.schedule + +Schedule is a rotation schedule - used in automatic mode to switch between phases. + +| Name | Type | Required | Description | +|----------------|--------------|----------|-----------------------------------------------------------------------| +| standby | RFC3339 time | | Standby specifies time to switch to the "Standby" phase. | +| update_clients | RFC3339 time | | UpdateClients specifies time to switch to the "Update clients" phase | +| update_servers | RFC3339 time | | UpdateServers specifies time to switch to the "Update servers" phase. | + +Example: + +``` +resource "teleport_server" "ssh_agentless" { + version = "v2" + sub_kind = "openssh" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + } +} + +``` + ## teleport_session_recording_config | Name | Type | Required | Description | diff --git a/terraform/test/fixtures/server_openssh_0_create.tf b/terraform/test/fixtures/server_openssh_0_create.tf new file mode 100644 index 000000000..ff699594f --- /dev/null +++ b/terraform/test/fixtures/server_openssh_0_create.tf @@ -0,0 +1,11 @@ +resource "teleport_server" "test" { + version = "v2" + sub_kind = "openssh" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + } +} diff --git a/terraform/test/fixtures/server_openssh_1_update.tf b/terraform/test/fixtures/server_openssh_1_update.tf new file mode 100644 index 000000000..2a2071a9e --- /dev/null +++ b/terraform/test/fixtures/server_openssh_1_update.tf @@ -0,0 +1,11 @@ +resource "teleport_server" "test" { + version = "v2" + sub_kind = "openssh" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:23" + hostname = "test.local" + } +} diff --git a/terraform/test/main_test.go b/terraform/test/main_test.go index f797cdac3..a350a5e0e 100644 --- a/terraform/test/main_test.go +++ b/terraform/test/main_test.go @@ -114,6 +114,7 @@ func (s *TerraformBaseSuite) SetupSuite() { Allow: types.RoleConditions{ DatabaseLabels: types.Labels(map[string]utils.Strings{"*": []string{"*"}}), AppLabels: types.Labels(map[string]utils.Strings{"*": []string{"*"}}), + NodeLabels: map[string]utils.Strings{"*": []string{"*"}}, Rules: []types.Rule{ types.NewRule("token", unrestricted), types.NewRule("role", unrestricted), @@ -131,6 +132,7 @@ func (s *TerraformBaseSuite) SetupSuite() { types.NewRule("login_rule", unrestricted), types.NewRule("device", unrestricted), types.NewRule("access_list", unrestricted), + types.NewRule("node", unrestricted), }, Logins: []string{me.Username}, }, diff --git a/terraform/test/server_test.go b/terraform/test/server_test.go new file mode 100644 index 000000000..c2f555839 --- /dev/null +++ b/terraform/test/server_test.go @@ -0,0 +1,72 @@ +/* +Copyright 2015-2021 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 test + +import ( + "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/trace" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func (s *TerraformSuite) TestServer() { + checkServerDestroyed := func(state *terraform.State) error { + _, err := s.client.GetNode(s.Context(), defaults.Namespace, "test") + if trace.IsNotFound(err) { + return nil + } + + return err + } + + name := "teleport_server.test" + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkServerDestroyed, + Steps: []resource.TestStep{ + { + Config: s.getFixture("server_openssh_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", "node"), + resource.TestCheckResourceAttr(name, "sub_kind", "openssh"), + resource.TestCheckResourceAttr(name, "version", "v2"), + resource.TestCheckResourceAttr(name, "spec.addr", "127.0.0.1:22"), + resource.TestCheckResourceAttr(name, "spec.hostname", "test.local"), + ), + }, + { + Config: s.getFixture("server_openssh_0_create.tf"), + PlanOnly: true, + }, + { + Config: s.getFixture("server_openssh_1_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", "node"), + resource.TestCheckResourceAttr(name, "sub_kind", "openssh"), + resource.TestCheckResourceAttr(name, "version", "v2"), + resource.TestCheckResourceAttr(name, "spec.addr", "127.0.0.1:23"), + resource.TestCheckResourceAttr(name, "spec.hostname", "test.local"), + ), + }, + { + Config: s.getFixture("server_openssh_1_update.tf"), + PlanOnly: true, + }, + }, + }) +} diff --git a/terraform/tfschema/types_terraform.go b/terraform/tfschema/types_terraform.go index 2b567802b..0bc80088d 100644 --- a/terraform/tfschema/types_terraform.go +++ b/terraform/tfschema/types_terraform.go @@ -516,6 +516,222 @@ func GenSchemaDatabaseV3(ctx context.Context) (github_com_hashicorp_terraform_pl }}, nil } +// GenSchemaServerV2 returns tfsdk.Schema definition for ServerV2 +func GenSchemaServerV2(ctx context.Context) (github_com_hashicorp_terraform_plugin_framework_tfsdk.Schema, github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics) { + return github_com_hashicorp_terraform_plugin_framework_tfsdk.Schema{Attributes: map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "id": { + Computed: true, + Optional: false, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Required: false, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "kind": { + Computed: true, + Description: "Kind is a resource kind", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "metadata": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "description": { + Description: "Description is object description", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "expires": { + Description: "Expires is a global expiry time header can be set on any resource in the system.", + Optional: true, + Type: UseRFC3339Time(), + Validators: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributeValidator{MustTimeBeInFuture()}, + }, + "labels": { + Description: "Labels is a set of labels", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.MapType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }, + "name": { + Description: "Name is an object name", + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()}, + Required: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "namespace": { + Computed: true, + Description: "Namespace is object namespace. The field should be called \"namespace\" when it returns in Teleport 2.4.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "revision": { + Description: "Revision is an opaque identifier which tracks the versions of a resource over time. Clients should ignore and not alter its value but must return the revision in any updates of a resource.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Metadata is resource metadata", + Optional: true, + }, + "spec": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "addr": { + Description: "Addr is a host:port address where this server can be reached.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "cloud_metadata": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{"aws": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "account_id": { + Description: "AccountID is an AWS account ID.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "instance_id": { + Description: "InstanceID is an EC2 instance ID.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "integration": { + Description: "Integration is the integration name that added this Node. When connecting to it, it will use this integration to issue AWS API calls in order to set up the connection. This includes sending an SSH Key and then opening a tunnel (EC2 Instance Connect Endpoint) so Teleport can connect to it.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "region": { + Description: "Region is the AWS EC2 Instance Region.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "subnet_id": { + Description: "SubnetID is the Subnet ID in use by the instance.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "vpc_id": { + Description: "VPCID is the AWS VPC ID where the Instance is running.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "AWSInfo contains attributes to match to an EC2 instance.", + Optional: true, + }}), + Description: "CloudMetadata contains info about the cloud instance the server is running on, if any.", + Optional: true, + }, + "hostname": { + Description: "Hostname is server hostname", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "peer_addr": { + Description: "PeerAddr is the address a proxy server is reachable at by its peer proxies.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "proxy_ids": { + Description: "ProxyIDs is a list of proxy IDs this server is expected to be connected to.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }, + "public_addrs": { + Description: "PublicAddrs is a list of public addresses where this server can be reached.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.ListType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }, + "rotation": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "current_id": { + Description: "CurrentID is the ID of the rotation operation to differentiate between rotation attempts.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "grace_period": { + Description: "GracePeriod is a period during which old and new CA are valid for checking purposes, but only new CA is issuing certificates.", + Optional: true, + Type: DurationType{}, + }, + "last_rotated": { + Description: "LastRotated specifies the last time of the completed rotation.", + Optional: true, + Type: UseRFC3339Time(), + }, + "mode": { + Description: "Mode sets manual or automatic rotation mode.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "phase": { + Description: "Phase is the current rotation phase.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "schedule": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "standby": { + Description: "Standby specifies time to switch to the \"Standby\" phase.", + Optional: true, + Type: UseRFC3339Time(), + }, + "update_clients": { + Description: "UpdateClients specifies time to switch to the \"Update clients\" phase", + Optional: true, + Type: UseRFC3339Time(), + }, + "update_servers": { + Description: "UpdateServers specifies time to switch to the \"Update servers\" phase.", + Optional: true, + Type: UseRFC3339Time(), + }, + }), + Description: "Schedule is a rotation schedule - used in automatic mode to switch between phases.", + Optional: true, + }, + "started": { + Description: "Started is set to the time when rotation has been started in case if the state of the rotation is \"in_progress\".", + Optional: true, + Type: UseRFC3339Time(), + }, + "state": { + Description: "State could be one of \"init\" or \"in_progress\".", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Rotation specifies server rotation", + Optional: true, + }, + "use_tunnel": { + Description: "UseTunnel indicates that connections to this server should occur over a reverse tunnel.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.BoolType, + }, + "version": { + Description: "TeleportVersion is the teleport version that the server is running on", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Spec is a server spec", + Optional: true, + }, + "sub_kind": { + Description: "SubKind is an optional resource sub kind, used in some resources", + Required: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + Validators: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributeValidator{UseValueIn([]string{"openssh", "openssh-ec2-ice"})}, + }, + "version": { + Description: "Version is version", + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown(), github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()}, + Required: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + Validators: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributeValidator{UseVersionBetween(2, 2)}, + }, + }}, nil +} + // GenSchemaAppV3 returns tfsdk.Schema definition for AppV3 func GenSchemaAppV3(ctx context.Context) (github_com_hashicorp_terraform_plugin_framework_tfsdk.Schema, github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics) { return github_com_hashicorp_terraform_plugin_framework_tfsdk.Schema{Attributes: map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ @@ -7398,6 +7614,1685 @@ func CopyDatabaseV3ToTerraform(ctx context.Context, obj *github_com_gravitationa return diags } +// CopyServerV2FromTerraform copies contents of the source Terraform object into a target struct +func CopyServerV2FromTerraform(_ context.Context, tf github_com_hashicorp_terraform_plugin_framework_types.Object, obj *github_com_gravitational_teleport_api_types.ServerV2) github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics { + var diags github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics + { + a, ok := tf.Attrs["kind"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Kind"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Kind = t + } + } + } + { + a, ok := tf.Attrs["sub_kind"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.SubKind"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.SubKind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.SubKind = t + } + } + } + { + a, ok := tf.Attrs["version"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Version"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Version", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Version = t + } + } + } + { + a, ok := tf.Attrs["metadata"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Metadata"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Metadata", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Metadata = github_com_gravitational_teleport_api_types.Metadata{} + if !v.Null && !v.Unknown { + tf := v + obj := &obj.Metadata + { + a, ok := tf.Attrs["name"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Metadata.Name"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Metadata.Name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Name = t + } + } + } + { + a, ok := tf.Attrs["namespace"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Metadata.Namespace"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Metadata.Namespace", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Namespace = t + } + } + } + { + a, ok := tf.Attrs["description"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Metadata.Description"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Metadata.Description", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Description = t + } + } + } + { + a, ok := tf.Attrs["labels"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Metadata.Labels"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Map) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Metadata.Labels", "github.com/hashicorp/terraform-plugin-framework/types.Map"}) + } else { + obj.Labels = make(map[string]string, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Metadata.Labels", "github_com_hashicorp_terraform_plugin_framework_types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Labels[k] = t + } + } + } + } + } + } + { + a, ok := tf.Attrs["expires"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Metadata.Expires"}) + } else { + v, ok := a.(TimeValue) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Metadata.Expires", "TimeValue"}) + } else { + var t *time.Time + if !v.Null && !v.Unknown { + c := time.Time(v.Value) + t = &c + } + obj.Expires = t + } + } + } + { + a, ok := tf.Attrs["revision"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Metadata.Revision"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Metadata.Revision", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Revision = t + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["spec"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Spec = github_com_gravitational_teleport_api_types.ServerSpecV2{} + if !v.Null && !v.Unknown { + tf := v + obj := &obj.Spec + { + a, ok := tf.Attrs["addr"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Addr"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Addr", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Addr = t + } + } + } + { + a, ok := tf.Attrs["hostname"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Hostname"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Hostname", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Hostname = t + } + } + } + { + a, ok := tf.Attrs["rotation"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Rotation = github_com_gravitational_teleport_api_types.Rotation{} + if !v.Null && !v.Unknown { + tf := v + obj := &obj.Rotation + { + a, ok := tf.Attrs["state"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.State"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.State", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.State = t + } + } + } + { + a, ok := tf.Attrs["phase"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.Phase"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.Phase", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Phase = t + } + } + } + { + a, ok := tf.Attrs["mode"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.Mode"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.Mode", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Mode = t + } + } + } + { + a, ok := tf.Attrs["current_id"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.CurrentID"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.CurrentID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.CurrentID = t + } + } + } + { + a, ok := tf.Attrs["started"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.Started"}) + } else { + v, ok := a.(TimeValue) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.Started", "TimeValue"}) + } else { + var t time.Time + if !v.Null && !v.Unknown { + t = time.Time(v.Value) + } + obj.Started = t + } + } + } + { + a, ok := tf.Attrs["grace_period"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.GracePeriod"}) + } else { + v, ok := a.(DurationValue) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.GracePeriod", "DurationValue"}) + } else { + var t github_com_gravitational_teleport_api_types.Duration + if !v.Null && !v.Unknown { + t = github_com_gravitational_teleport_api_types.Duration(v.Value) + } + obj.GracePeriod = t + } + } + } + { + a, ok := tf.Attrs["last_rotated"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.LastRotated"}) + } else { + v, ok := a.(TimeValue) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.LastRotated", "TimeValue"}) + } else { + var t time.Time + if !v.Null && !v.Unknown { + t = time.Time(v.Value) + } + obj.LastRotated = t + } + } + } + { + a, ok := tf.Attrs["schedule"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.Schedule"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.Schedule", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Schedule = github_com_gravitational_teleport_api_types.RotationSchedule{} + if !v.Null && !v.Unknown { + tf := v + obj := &obj.Schedule + { + a, ok := tf.Attrs["update_clients"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.Schedule.UpdateClients"}) + } else { + v, ok := a.(TimeValue) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.Schedule.UpdateClients", "TimeValue"}) + } else { + var t time.Time + if !v.Null && !v.Unknown { + t = time.Time(v.Value) + } + obj.UpdateClients = t + } + } + } + { + a, ok := tf.Attrs["update_servers"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.Schedule.UpdateServers"}) + } else { + v, ok := a.(TimeValue) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.Schedule.UpdateServers", "TimeValue"}) + } else { + var t time.Time + if !v.Null && !v.Unknown { + t = time.Time(v.Value) + } + obj.UpdateServers = t + } + } + } + { + a, ok := tf.Attrs["standby"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Rotation.Schedule.Standby"}) + } else { + v, ok := a.(TimeValue) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Rotation.Schedule.Standby", "TimeValue"}) + } else { + var t time.Time + if !v.Null && !v.Unknown { + t = time.Time(v.Value) + } + obj.Standby = t + } + } + } + } + } + } + } + } + } + } + } + { + a, ok := tf.Attrs["use_tunnel"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.UseTunnel"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Bool) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.UseTunnel", "github.com/hashicorp/terraform-plugin-framework/types.Bool"}) + } else { + var t bool + if !v.Null && !v.Unknown { + t = bool(v.Value) + } + obj.UseTunnel = t + } + } + } + { + a, ok := tf.Attrs["version"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.Version"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.Version", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Version = t + } + } + } + { + a, ok := tf.Attrs["peer_addr"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.PeerAddr"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.PeerAddr", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.PeerAddr = t + } + } + } + { + a, ok := tf.Attrs["proxy_ids"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.ProxyIDs"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.ProxyIDs", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.ProxyIDs = make([]string, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.ProxyIDs", "github_com_hashicorp_terraform_plugin_framework_types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.ProxyIDs[k] = t + } + } + } + } + } + } + { + a, ok := tf.Attrs["public_addrs"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.public_addrs"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.public_addrs", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.PublicAddrs = make([]string, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.public_addrs", "github_com_hashicorp_terraform_plugin_framework_types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.PublicAddrs[k] = t + } + } + } + } + } + } + { + a, ok := tf.Attrs["cloud_metadata"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.CloudMetadata"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.CloudMetadata", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.CloudMetadata = nil + if !v.Null && !v.Unknown { + tf := v + obj.CloudMetadata = &github_com_gravitational_teleport_api_types.CloudMetadata{} + obj := obj.CloudMetadata + { + a, ok := tf.Attrs["aws"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.CloudMetadata.AWS"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.AWS = nil + if !v.Null && !v.Unknown { + tf := v + obj.AWS = &github_com_gravitational_teleport_api_types.AWSInfo{} + obj := obj.AWS + { + a, ok := tf.Attrs["account_id"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.AccountID"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.AccountID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.AccountID = t + } + } + } + { + a, ok := tf.Attrs["instance_id"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.InstanceID"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.InstanceID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.InstanceID = t + } + } + } + { + a, ok := tf.Attrs["region"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.Region"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.Region", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Region = t + } + } + } + { + a, ok := tf.Attrs["vpc_id"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.VPCID"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.VPCID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.VPCID = t + } + } + } + { + a, ok := tf.Attrs["integration"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.Integration"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.Integration", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Integration = t + } + } + } + { + a, ok := tf.Attrs["subnet_id"] + if !ok { + diags.Append(attrReadMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.SubnetID"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.SubnetID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.SubnetID = t + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return diags +} + +// CopyServerV2ToTerraform copies contents of the source Terraform object into a target struct +func CopyServerV2ToTerraform(ctx context.Context, obj *github_com_gravitational_teleport_api_types.ServerV2, tf *github_com_hashicorp_terraform_plugin_framework_types.Object) github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics { + var diags github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics + tf.Null = false + tf.Unknown = false + if tf.Attrs == nil { + tf.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value) + } + { + t, ok := tf.AttrTypes["kind"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Kind"}) + } else { + v, ok := tf.Attrs["kind"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Kind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Kind) == "" + } + v.Value = string(obj.Kind) + v.Unknown = false + tf.Attrs["kind"] = v + } + } + { + t, ok := tf.AttrTypes["sub_kind"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.SubKind"}) + } else { + v, ok := tf.Attrs["sub_kind"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.SubKind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.SubKind", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.SubKind) == "" + } + v.Value = string(obj.SubKind) + v.Unknown = false + tf.Attrs["sub_kind"] = v + } + } + { + t, ok := tf.AttrTypes["version"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Version"}) + } else { + v, ok := tf.Attrs["version"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Version", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Version", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Version) == "" + } + v.Value = string(obj.Version) + v.Unknown = false + tf.Attrs["version"] = v + } + } + { + a, ok := tf.AttrTypes["metadata"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Metadata"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Metadata", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["metadata"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + { + obj := obj.Metadata + tf := &v + { + t, ok := tf.AttrTypes["name"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Metadata.Name"}) + } else { + v, ok := tf.Attrs["name"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Metadata.Name", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Metadata.Name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Name) == "" + } + v.Value = string(obj.Name) + v.Unknown = false + tf.Attrs["name"] = v + } + } + { + t, ok := tf.AttrTypes["namespace"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Metadata.Namespace"}) + } else { + v, ok := tf.Attrs["namespace"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Metadata.Namespace", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Metadata.Namespace", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Namespace) == "" + } + v.Value = string(obj.Namespace) + v.Unknown = false + tf.Attrs["namespace"] = v + } + } + { + t, ok := tf.AttrTypes["description"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Metadata.Description"}) + } else { + v, ok := tf.Attrs["description"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Metadata.Description", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Metadata.Description", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Description) == "" + } + v.Value = string(obj.Description) + v.Unknown = false + tf.Attrs["description"] = v + } + } + { + a, ok := tf.AttrTypes["labels"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Metadata.Labels"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.MapType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Metadata.Labels", "github.com/hashicorp/terraform-plugin-framework/types.MapType"}) + } else { + c, ok := tf.Attrs["labels"].(github_com_hashicorp_terraform_plugin_framework_types.Map) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.Map{ + + ElemType: o.ElemType, + Elems: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Labels)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Labels)) + } + } + if obj.Labels != nil { + t := o.ElemType + for k, a := range obj.Labels { + v, ok := tf.Attrs["labels"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Metadata.Labels", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Metadata.Labels", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = false + } + v.Value = string(a) + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Labels) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["labels"] = c + } + } + } + { + t, ok := tf.AttrTypes["expires"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Metadata.Expires"}) + } else { + v, ok := tf.Attrs["expires"].(TimeValue) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Metadata.Expires", err}) + } + v, ok = i.(TimeValue) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Metadata.Expires", "TimeValue"}) + } + v.Null = false + } + if obj.Expires == nil { + v.Null = true + } else { + v.Null = false + v.Value = time.Time(*obj.Expires) + } + v.Unknown = false + tf.Attrs["expires"] = v + } + } + { + t, ok := tf.AttrTypes["revision"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Metadata.Revision"}) + } else { + v, ok := tf.Attrs["revision"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Metadata.Revision", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Metadata.Revision", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Revision) == "" + } + v.Value = string(obj.Revision) + v.Unknown = false + tf.Attrs["revision"] = v + } + } + } + v.Unknown = false + tf.Attrs["metadata"] = v + } + } + } + { + a, ok := tf.AttrTypes["spec"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["spec"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + { + obj := obj.Spec + tf := &v + { + t, ok := tf.AttrTypes["addr"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Addr"}) + } else { + v, ok := tf.Attrs["addr"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Addr", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Addr", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Addr) == "" + } + v.Value = string(obj.Addr) + v.Unknown = false + tf.Attrs["addr"] = v + } + } + { + t, ok := tf.AttrTypes["hostname"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Hostname"}) + } else { + v, ok := tf.Attrs["hostname"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Hostname", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Hostname", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Hostname) == "" + } + v.Value = string(obj.Hostname) + v.Unknown = false + tf.Attrs["hostname"] = v + } + } + { + a, ok := tf.AttrTypes["rotation"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["rotation"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + { + obj := obj.Rotation + tf := &v + { + t, ok := tf.AttrTypes["state"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.State"}) + } else { + v, ok := tf.Attrs["state"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.State", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.State", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.State) == "" + } + v.Value = string(obj.State) + v.Unknown = false + tf.Attrs["state"] = v + } + } + { + t, ok := tf.AttrTypes["phase"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.Phase"}) + } else { + v, ok := tf.Attrs["phase"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.Phase", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.Phase", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Phase) == "" + } + v.Value = string(obj.Phase) + v.Unknown = false + tf.Attrs["phase"] = v + } + } + { + t, ok := tf.AttrTypes["mode"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.Mode"}) + } else { + v, ok := tf.Attrs["mode"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.Mode", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.Mode", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Mode) == "" + } + v.Value = string(obj.Mode) + v.Unknown = false + tf.Attrs["mode"] = v + } + } + { + t, ok := tf.AttrTypes["current_id"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.CurrentID"}) + } else { + v, ok := tf.Attrs["current_id"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.CurrentID", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.CurrentID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.CurrentID) == "" + } + v.Value = string(obj.CurrentID) + v.Unknown = false + tf.Attrs["current_id"] = v + } + } + { + t, ok := tf.AttrTypes["started"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.Started"}) + } else { + v, ok := tf.Attrs["started"].(TimeValue) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.Started", err}) + } + v, ok = i.(TimeValue) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.Started", "TimeValue"}) + } + v.Null = false + } + v.Value = time.Time(obj.Started) + v.Unknown = false + tf.Attrs["started"] = v + } + } + { + t, ok := tf.AttrTypes["grace_period"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.GracePeriod"}) + } else { + v, ok := tf.Attrs["grace_period"].(DurationValue) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.GracePeriod", err}) + } + v, ok = i.(DurationValue) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.GracePeriod", "DurationValue"}) + } + v.Null = false + } + v.Value = time.Duration(obj.GracePeriod) + v.Unknown = false + tf.Attrs["grace_period"] = v + } + } + { + t, ok := tf.AttrTypes["last_rotated"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.LastRotated"}) + } else { + v, ok := tf.Attrs["last_rotated"].(TimeValue) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.LastRotated", err}) + } + v, ok = i.(TimeValue) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.LastRotated", "TimeValue"}) + } + v.Null = false + } + v.Value = time.Time(obj.LastRotated) + v.Unknown = false + tf.Attrs["last_rotated"] = v + } + } + { + a, ok := tf.AttrTypes["schedule"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.Schedule"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.Schedule", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["schedule"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + { + obj := obj.Schedule + tf := &v + { + t, ok := tf.AttrTypes["update_clients"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.Schedule.UpdateClients"}) + } else { + v, ok := tf.Attrs["update_clients"].(TimeValue) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.Schedule.UpdateClients", err}) + } + v, ok = i.(TimeValue) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.Schedule.UpdateClients", "TimeValue"}) + } + v.Null = false + } + v.Value = time.Time(obj.UpdateClients) + v.Unknown = false + tf.Attrs["update_clients"] = v + } + } + { + t, ok := tf.AttrTypes["update_servers"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.Schedule.UpdateServers"}) + } else { + v, ok := tf.Attrs["update_servers"].(TimeValue) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.Schedule.UpdateServers", err}) + } + v, ok = i.(TimeValue) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.Schedule.UpdateServers", "TimeValue"}) + } + v.Null = false + } + v.Value = time.Time(obj.UpdateServers) + v.Unknown = false + tf.Attrs["update_servers"] = v + } + } + { + t, ok := tf.AttrTypes["standby"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Rotation.Schedule.Standby"}) + } else { + v, ok := tf.Attrs["standby"].(TimeValue) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Rotation.Schedule.Standby", err}) + } + v, ok = i.(TimeValue) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Rotation.Schedule.Standby", "TimeValue"}) + } + v.Null = false + } + v.Value = time.Time(obj.Standby) + v.Unknown = false + tf.Attrs["standby"] = v + } + } + } + v.Unknown = false + tf.Attrs["schedule"] = v + } + } + } + } + v.Unknown = false + tf.Attrs["rotation"] = v + } + } + } + { + t, ok := tf.AttrTypes["use_tunnel"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.UseTunnel"}) + } else { + v, ok := tf.Attrs["use_tunnel"].(github_com_hashicorp_terraform_plugin_framework_types.Bool) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.UseTunnel", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.Bool) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.UseTunnel", "github.com/hashicorp/terraform-plugin-framework/types.Bool"}) + } + v.Null = bool(obj.UseTunnel) == false + } + v.Value = bool(obj.UseTunnel) + v.Unknown = false + tf.Attrs["use_tunnel"] = v + } + } + { + t, ok := tf.AttrTypes["version"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.Version"}) + } else { + v, ok := tf.Attrs["version"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.Version", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.Version", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Version) == "" + } + v.Value = string(obj.Version) + v.Unknown = false + tf.Attrs["version"] = v + } + } + { + t, ok := tf.AttrTypes["peer_addr"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.PeerAddr"}) + } else { + v, ok := tf.Attrs["peer_addr"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.PeerAddr", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.PeerAddr", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.PeerAddr) == "" + } + v.Value = string(obj.PeerAddr) + v.Unknown = false + tf.Attrs["peer_addr"] = v + } + } + { + a, ok := tf.AttrTypes["proxy_ids"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.ProxyIDs"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.ProxyIDs", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["proxy_ids"].(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.List{ + + ElemType: o.ElemType, + Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.ProxyIDs)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.ProxyIDs)) + } + } + if obj.ProxyIDs != nil { + t := o.ElemType + if len(obj.ProxyIDs) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.ProxyIDs)) + } + for k, a := range obj.ProxyIDs { + v, ok := tf.Attrs["proxy_ids"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.ProxyIDs", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.ProxyIDs", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(a) == "" + } + v.Value = string(a) + v.Unknown = false + c.Elems[k] = v + } + if len(obj.ProxyIDs) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["proxy_ids"] = c + } + } + } + { + a, ok := tf.AttrTypes["public_addrs"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.public_addrs"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.public_addrs", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["public_addrs"].(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + c = github_com_hashicorp_terraform_plugin_framework_types.List{ + + ElemType: o.ElemType, + Elems: make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.PublicAddrs)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.PublicAddrs)) + } + } + if obj.PublicAddrs != nil { + t := o.ElemType + if len(obj.PublicAddrs) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.PublicAddrs)) + } + for k, a := range obj.PublicAddrs { + v, ok := tf.Attrs["public_addrs"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.public_addrs", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.public_addrs", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(a) == "" + } + v.Value = string(a) + v.Unknown = false + c.Elems[k] = v + } + if len(obj.PublicAddrs) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["public_addrs"] = c + } + } + } + { + a, ok := tf.AttrTypes["cloud_metadata"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.CloudMetadata"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.CloudMetadata", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["cloud_metadata"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.CloudMetadata == nil { + v.Null = true + } else { + obj := obj.CloudMetadata + tf := &v + { + a, ok := tf.AttrTypes["aws"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.CloudMetadata.AWS"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS", "github.com/hashicorp/terraform-plugin-framework/types.ObjectType"}) + } else { + v, ok := tf.Attrs["aws"].(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + v = github_com_hashicorp_terraform_plugin_framework_types.Object{ + + AttrTypes: o.AttrTypes, + Attrs: make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(o.AttrTypes)), + } + } else { + if v.Attrs == nil { + v.Attrs = make(map[string]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(tf.AttrTypes)) + } + } + if obj.AWS == nil { + v.Null = true + } else { + obj := obj.AWS + tf := &v + { + t, ok := tf.AttrTypes["account_id"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.AccountID"}) + } else { + v, ok := tf.Attrs["account_id"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.CloudMetadata.AWS.AccountID", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.AccountID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.AccountID) == "" + } + v.Value = string(obj.AccountID) + v.Unknown = false + tf.Attrs["account_id"] = v + } + } + { + t, ok := tf.AttrTypes["instance_id"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.InstanceID"}) + } else { + v, ok := tf.Attrs["instance_id"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.CloudMetadata.AWS.InstanceID", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.InstanceID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.InstanceID) == "" + } + v.Value = string(obj.InstanceID) + v.Unknown = false + tf.Attrs["instance_id"] = v + } + } + { + t, ok := tf.AttrTypes["region"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.Region"}) + } else { + v, ok := tf.Attrs["region"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.CloudMetadata.AWS.Region", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.Region", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Region) == "" + } + v.Value = string(obj.Region) + v.Unknown = false + tf.Attrs["region"] = v + } + } + { + t, ok := tf.AttrTypes["vpc_id"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.VPCID"}) + } else { + v, ok := tf.Attrs["vpc_id"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.CloudMetadata.AWS.VPCID", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.VPCID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.VPCID) == "" + } + v.Value = string(obj.VPCID) + v.Unknown = false + tf.Attrs["vpc_id"] = v + } + } + { + t, ok := tf.AttrTypes["integration"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.Integration"}) + } else { + v, ok := tf.Attrs["integration"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.CloudMetadata.AWS.Integration", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.Integration", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Integration) == "" + } + v.Value = string(obj.Integration) + v.Unknown = false + tf.Attrs["integration"] = v + } + } + { + t, ok := tf.AttrTypes["subnet_id"] + if !ok { + diags.Append(attrWriteMissingDiag{"ServerV2.Spec.CloudMetadata.AWS.SubnetID"}) + } else { + v, ok := tf.Attrs["subnet_id"].(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + i, err := t.ValueFromTerraform(ctx, github_com_hashicorp_terraform_plugin_go_tftypes.NewValue(t.TerraformType(ctx), nil)) + if err != nil { + diags.Append(attrWriteGeneralError{"ServerV2.Spec.CloudMetadata.AWS.SubnetID", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ServerV2.Spec.CloudMetadata.AWS.SubnetID", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.SubnetID) == "" + } + v.Value = string(obj.SubnetID) + v.Unknown = false + tf.Attrs["subnet_id"] = v + } + } + } + v.Unknown = false + tf.Attrs["aws"] = v + } + } + } + } + v.Unknown = false + tf.Attrs["cloud_metadata"] = v + } + } + } + } + v.Unknown = false + tf.Attrs["spec"] = v + } + } + } + return diags +} + // CopyAppV3FromTerraform copies contents of the source Terraform object into a target struct func CopyAppV3FromTerraform(_ context.Context, tf github_com_hashicorp_terraform_plugin_framework_types.Object, obj *github_com_gravitational_teleport_api_types.AppV3) github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics { var diags github_com_hashicorp_terraform_plugin_framework_diag.Diagnostics diff --git a/terraform/tfschema/validators.go b/terraform/tfschema/validators.go index 09f2bb39e..1484823eb 100644 --- a/terraform/tfschema/validators.go +++ b/terraform/tfschema/validators.go @@ -19,6 +19,7 @@ package tfschema import ( "context" fmt "fmt" + "slices" "strings" "time" @@ -228,3 +229,48 @@ func (v AnyOfValidator) Validate(ctx context.Context, req tfsdk.ValidateAttribut fmt.Sprintf("AnyOf '%s' keys must be present", strings.Join(v.Keys, ", ")), ) } + +// UseValueIn creates a StringValueValidator +func UseValueIn(subkinds []string) tfsdk.AttributeValidator { + return StringValueValidator{subkinds} +} + +// StringValueValidator validates that a resource string field is in a set of allowed values. +type StringValueValidator struct { + AllowedValues []string +} + +// Description returns validator description +func (v StringValueValidator) Description(_ context.Context) string { + return fmt.Sprintf("Checks that string field is one of %v", v.AllowedValues) +} + +// MarkdownDescription returns validator markdown description +func (v StringValueValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("Checks that string field is one of %v", v.AllowedValues) +} + +// Validate performs the validation. +func (v StringValueValidator) Validate(_ context.Context, req tfsdk.ValidateAttributeRequest, resp *tfsdk.ValidateAttributeResponse) { + if req.AttributeConfig == nil { + return + } + + value, ok := req.AttributeConfig.(types.String) + if !ok { + resp.Diagnostics.AddError("Version validation error", fmt.Sprintf("Attribute %v can not be converted to StringValue", req.AttributePath.String())) + return + } + + if value.Null || value.Unknown { + if !slices.Contains(v.AllowedValues, "") { + resp.Diagnostics.AddError("Field validation error", fmt.Sprintf("Attribute %v (%v) is unset but empty string is not a valid value. (vXX)", req.AttributePath.String(), value.Value)) + } + return + } + + if !slices.Contains(v.AllowedValues, value.Value) { + resp.Diagnostics.AddError("Field validation error", fmt.Sprintf("Attribute %v (%v) is not in the allowed set (%v).", req.AttributePath.String(), value.Value, v.AllowedValues)) + } + return +} From b358b0e509d732d61b40d97cfb068de9e914fde6 Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Wed, 21 Feb 2024 14:26:14 -0500 Subject: [PATCH 02/11] address feedback: list order --- terraform/protoc-gen-terraform-teleport.yaml | 48 ++++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/terraform/protoc-gen-terraform-teleport.yaml b/terraform/protoc-gen-terraform-teleport.yaml index 403dd5a65..6bbdcc1ee 100644 --- a/terraform/protoc-gen-terraform-teleport.yaml +++ b/terraform/protoc-gen-terraform-teleport.yaml @@ -17,10 +17,10 @@ types: - "ProvisionTokenV2" - "RoleV6" - "SAMLConnectorV2" + - "ServerV2" - "SessionRecordingConfigV2" - "TrustedClusterV2" - "UserV2" - - "ServerV2" # id field is required for integration tests. It is not used by provider. # We have to add it manually (might be removed in the future versions). @@ -102,28 +102,28 @@ injected_fields: computed: true plan_modifiers: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - SessionRecordingConfigV2: + ServerV2: - name: id type: github.com/hashicorp/terraform-plugin-framework/types.StringType computed: true plan_modifiers: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - TrustedClusterV2: + SessionRecordingConfigV2: - name: id type: github.com/hashicorp/terraform-plugin-framework/types.StringType computed: true plan_modifiers: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - UserV2: + TrustedClusterV2: - name: id type: github.com/hashicorp/terraform-plugin-framework/types.StringType computed: true plan_modifiers: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - ServerV2: + UserV2: - name: id type: github.com/hashicorp/terraform-plugin-framework/types.StringType @@ -152,6 +152,9 @@ exclude_fields: - "RoleV6.Spec.Allow.Namespaces" # These fields are not settable via API - "RoleV6.Spec.Deny.Namespaces" + # Server + - "ServerSpecV2.CmdLabels" + # SessionRecordingConfig - "SessionRecordingConfigV2.Metadata.Name" # It's a singleton resource @@ -161,9 +164,6 @@ exclude_fields: - "UserSpecV2.CreatedBy" - "UserSpecV2.Status" - # Server - - "ServerSpecV2.CmdLabels" - name_overrides: # These fields will be marked as Computed: true @@ -252,6 +252,9 @@ computed_fields: - "SAMLConnectorV2.Spec.EncryptionKeyPair.Cert" - "SAMLConnectorV2.Kind" + # Server + - "ServerV2.Kind" + # Session recording - "SessionRecordingConfigV2.Spec.Mode" - "SessionRecordingConfigV2.Kind" @@ -262,9 +265,6 @@ computed_fields: # User - "UserV2.Kind" - # Server - - "ServerV2.Kind" - # These fields will be marked as Required: true required_fields: # App @@ -304,7 +304,7 @@ required_fields: - "ProvisionTokenV2.Spec.Roles" - "ProvisionTokenV2.Version" - # Role + # Role - "RoleV6.Metadata.Name" - "RoleV6.Version" @@ -315,6 +315,11 @@ required_fields: - "SAMLConnectorV2.Metadata.Name" - "SAMLConnectorV2.Version" + # Server + - "ServerV2.Metadata.Name" + - "ServerV2.Version" + - "ServerV2.SubKind" + # Trusted cluster - "TrustedClusterV2.Metadata.Name" - "TrustedClusterV2.Version" @@ -324,11 +329,6 @@ required_fields: - "UserV2.Metadata.Name" - "UserV2.Version" - # Server - - "ServerV2.Metadata.Name" - - "ServerV2.Version" - - "ServerV2.SubKind" - - "SessionRecordingConfigV2.Version" - "ClusterMaintenanceConfigV1.Version" - "AuthPreferenceV2.Version" @@ -395,6 +395,9 @@ plan_modifiers: UserV2.Version: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + ServerV2.Version: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" SessionRecordingConfigV2.Version: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" @@ -404,9 +407,6 @@ plan_modifiers: AuthPreferenceV2.Version: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" - ServerV2.Version: - - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" - - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" validators: # Expires must be in the future @@ -440,6 +440,10 @@ validators: - UseVersionBetween(2,2) SAMLConnectorV2.Spec: - UseAnyOfValidator("entity_descriptor", "entity_descriptor_url") + ServerV2.Version: + - UseVersionBetween(2,2) + ServerV2.SubKind: + - UseValueIn([]string{"openssh", "openssh-ec2-ice"}) SessionRecordingConfigV2.Version: - UseVersionBetween(2,2) SessionRecordingConfigV2.Metadata.Labels: @@ -448,10 +452,6 @@ validators: - UseVersionBetween(2,2) ClusterMaintenanceConfigV1.Version: - UseVersionBetween(1,1) - ServerV2.Version: - - UseVersionBetween(2,2) - ServerV2.SubKind: - - UseValueIn([]string{"openssh", "openssh-ec2-ice"}) time_type: type: "TimeType" From 3e472ea2fb4f88c2ec2a400ceb4a9c5d4df0208d Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Wed, 21 Feb 2024 14:27:25 -0500 Subject: [PATCH 03/11] address feedback: consistent types in tests --- terraform/test/main_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/test/main_test.go b/terraform/test/main_test.go index a350a5e0e..c714ea628 100644 --- a/terraform/test/main_test.go +++ b/terraform/test/main_test.go @@ -112,8 +112,8 @@ func (s *TerraformBaseSuite) SetupSuite() { unrestricted := []string{"list", "create", "read", "update", "delete"} role, err := bootstrap.AddRole("terraform", types.RoleSpecV6{ Allow: types.RoleConditions{ - DatabaseLabels: types.Labels(map[string]utils.Strings{"*": []string{"*"}}), - AppLabels: types.Labels(map[string]utils.Strings{"*": []string{"*"}}), + DatabaseLabels: map[string]utils.Strings{"*": []string{"*"}}, + AppLabels: map[string]utils.Strings{"*": []string{"*"}}, NodeLabels: map[string]utils.Strings{"*": []string{"*"}}, Rules: []types.Rule{ types.NewRule("token", unrestricted), From 1e314678d939271f7b2680bddc7ed9639bb5d7fd Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Wed, 21 Feb 2024 14:32:21 -0500 Subject: [PATCH 04/11] address feedback: fix validator typo --- terraform/tfschema/validators.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/tfschema/validators.go b/terraform/tfschema/validators.go index 1484823eb..c08a1382c 100644 --- a/terraform/tfschema/validators.go +++ b/terraform/tfschema/validators.go @@ -258,13 +258,13 @@ func (v StringValueValidator) Validate(_ context.Context, req tfsdk.ValidateAttr value, ok := req.AttributeConfig.(types.String) if !ok { - resp.Diagnostics.AddError("Version validation error", fmt.Sprintf("Attribute %v can not be converted to StringValue", req.AttributePath.String())) + resp.Diagnostics.AddError("Field validation error", fmt.Sprintf("Attribute %v can not be converted to StringValue", req.AttributePath.String())) return } if value.Null || value.Unknown { if !slices.Contains(v.AllowedValues, "") { - resp.Diagnostics.AddError("Field validation error", fmt.Sprintf("Attribute %v (%v) is unset but empty string is not a valid value. (vXX)", req.AttributePath.String(), value.Value)) + resp.Diagnostics.AddError("Field validation error", fmt.Sprintf("Attribute %v (%v) is unset but empty string is not a valid value.", req.AttributePath.String(), value.Value)) } return } From 0d44b38c80cada6e5b9e2647e10d589f507a959c Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Thu, 22 Feb 2024 16:26:20 -0500 Subject: [PATCH 05/11] address feedback: add more tests --- .../fixtures/server_openssheice_0_create.tf | 21 ++ .../fixtures/server_openssheice_1_update.tf | 21 ++ terraform/test/server_test.go | 194 +++++++++++++++++- 3 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 terraform/test/fixtures/server_openssheice_0_create.tf create mode 100644 terraform/test/fixtures/server_openssheice_1_update.tf diff --git a/terraform/test/fixtures/server_openssheice_0_create.tf b/terraform/test/fixtures/server_openssheice_0_create.tf new file mode 100644 index 000000000..9d0dc4e59 --- /dev/null +++ b/terraform/test/fixtures/server_openssheice_0_create.tf @@ -0,0 +1,21 @@ +resource "teleport_server" "test" { + version = "v2" + sub_kind = "openssh-ec2-ice" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + cloud_metadata = { + aws = { + account_id = "123" + instance_id = "123" + region = "us-east-1" + vpc_id = "123" + integration = "foo" + subnet_id = "123" + } + } + } +} diff --git a/terraform/test/fixtures/server_openssheice_1_update.tf b/terraform/test/fixtures/server_openssheice_1_update.tf new file mode 100644 index 000000000..263072d6a --- /dev/null +++ b/terraform/test/fixtures/server_openssheice_1_update.tf @@ -0,0 +1,21 @@ +resource "teleport_server" "test" { + version = "v2" + sub_kind = "openssh-ec2-ice" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:23" + hostname = "test.local" + cloud_metadata = { + aws = { + account_id = "123" + instance_id = "123" + region = "us-east-1" + vpc_id = "123" + integration = "foo" + subnet_id = "123" + } + } + } +} diff --git a/terraform/test/server_test.go b/terraform/test/server_test.go index c2f555839..eb0014192 100644 --- a/terraform/test/server_test.go +++ b/terraform/test/server_test.go @@ -18,12 +18,15 @@ package test import ( "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/types" "github.com/gravitational/trace" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/stretchr/testify/require" + "time" ) -func (s *TerraformSuite) TestServer() { +func (s *TerraformSuite) TestOpenSSHServer() { checkServerDestroyed := func(state *terraform.State) error { _, err := s.client.GetNode(s.Context(), defaults.Namespace, "test") if trace.IsNotFound(err) { @@ -42,8 +45,8 @@ func (s *TerraformSuite) TestServer() { { Config: s.getFixture("server_openssh_0_create.tf"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, "kind", "node"), - resource.TestCheckResourceAttr(name, "sub_kind", "openssh"), + resource.TestCheckResourceAttr(name, "kind", types.KindNode), + resource.TestCheckResourceAttr(name, "sub_kind", types.SubKindOpenSSHNode), resource.TestCheckResourceAttr(name, "version", "v2"), resource.TestCheckResourceAttr(name, "spec.addr", "127.0.0.1:22"), resource.TestCheckResourceAttr(name, "spec.hostname", "test.local"), @@ -56,8 +59,8 @@ func (s *TerraformSuite) TestServer() { { Config: s.getFixture("server_openssh_1_update.tf"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, "kind", "node"), - resource.TestCheckResourceAttr(name, "sub_kind", "openssh"), + resource.TestCheckResourceAttr(name, "kind", types.KindNode), + resource.TestCheckResourceAttr(name, "sub_kind", types.SubKindOpenSSHNode), resource.TestCheckResourceAttr(name, "version", "v2"), resource.TestCheckResourceAttr(name, "spec.addr", "127.0.0.1:23"), resource.TestCheckResourceAttr(name, "spec.hostname", "test.local"), @@ -70,3 +73,184 @@ func (s *TerraformSuite) TestServer() { }, }) } + +func (s *TerraformSuite) TestImportOpenSSHServer() { + r := "teleport_server" + id := "test_import" + name := r + "." + id + + server := &types.ServerV2{ + Kind: types.KindNode, + SubKind: types.SubKindOpenSSHNode, + Version: types.V2, + Metadata: types.Metadata{ + Name: id, + }, + Spec: types.ServerSpecV2{ + Addr: "127.0.0.1:22", + Hostname: "foobar", + }, + } + err := server.CheckAndSetDefaults() + require.NoError(s.T(), err) + + _, err = s.client.UpsertNode(s.Context(), server) + require.NoError(s.T(), err) + + require.Eventually(s.T(), func() bool { + _, err = s.client.GetNode(s.Context(), defaults.Namespace, server.GetName()) + if trace.IsNotFound(err) { + return false + } + require.NoError(s.T(), err) + return true + }, 5*time.Second, time.Second) + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + Steps: []resource.TestStep{ + { + Config: s.terraformConfig + "\n" + `resource "` + r + `" "` + id + `" { }`, + ResourceName: name, + ImportState: true, + ImportStateId: id, + ImportStateCheck: func(state []*terraform.InstanceState) error { + require.Equal(s.T(), state[0].Attributes["kind"], types.KindNode) + require.Equal(s.T(), state[0].Attributes["sub_kind"], types.SubKindOpenSSHNode) + require.Equal(s.T(), state[0].Attributes["spec.addr"], "127.0.0.1:22") + require.Equal(s.T(), state[0].Attributes["spec.hostname"], "foobar") + + return nil + }, + }, + }, + }) +} + +func (s *TerraformSuite) TestOpenSSHEICEServer() { + checkServerDestroyed := func(state *terraform.State) error { + _, err := s.client.GetNode(s.Context(), defaults.Namespace, "test") + if trace.IsNotFound(err) { + return nil + } + + return err + } + + name := "teleport_server.test" + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkServerDestroyed, + Steps: []resource.TestStep{ + { + Config: s.getFixture("server_openssheice_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", types.KindNode), + resource.TestCheckResourceAttr(name, "sub_kind", types.SubKindOpenSSHEICENode), + resource.TestCheckResourceAttr(name, "version", "v2"), + resource.TestCheckResourceAttr(name, "spec.addr", "127.0.0.1:22"), + resource.TestCheckResourceAttr(name, "spec.hostname", "test.local"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.account_id", "123"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.instance_id", "123"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.region", "us-east-1"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.vpc_id", "123"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.integration", "foo"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.subnet_id", "123"), + ), + }, + { + Config: s.getFixture("server_openssheice_0_create.tf"), + PlanOnly: true, + }, + { + Config: s.getFixture("server_openssheice_1_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", types.KindNode), + resource.TestCheckResourceAttr(name, "sub_kind", types.SubKindOpenSSHEICENode), + resource.TestCheckResourceAttr(name, "version", "v2"), + resource.TestCheckResourceAttr(name, "spec.addr", "127.0.0.1:23"), + resource.TestCheckResourceAttr(name, "spec.hostname", "test.local"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.account_id", "123"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.instance_id", "123"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.region", "us-east-1"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.vpc_id", "123"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.integration", "foo"), + resource.TestCheckResourceAttr(name, "spec.cloud_metadata.aws.subnet_id", "123"), + ), + }, + { + Config: s.getFixture("server_openssheice_1_update.tf"), + PlanOnly: true, + }, + }, + }) +} + +func (s *TerraformSuite) TestImportOpenSSHEICEServer() { + r := "teleport_server" + id := "test_import" + name := r + "." + id + + server := &types.ServerV2{ + Kind: types.KindNode, + SubKind: types.SubKindOpenSSHEICENode, + Version: types.V2, + Metadata: types.Metadata{ + Name: id, + }, + Spec: types.ServerSpecV2{ + Addr: "127.0.0.1:22", + Hostname: "foobar", + CloudMetadata: &types.CloudMetadata{ + AWS: &types.AWSInfo{ + AccountID: "123", + InstanceID: "123", + Region: "us-east-1", + VPCID: "123", + Integration: "foo", + SubnetID: "123", + }, + }, + }, + } + err := server.CheckAndSetDefaults() + require.NoError(s.T(), err) + + _, err = s.client.UpsertNode(s.Context(), server) + require.NoError(s.T(), err) + + require.Eventually(s.T(), func() bool { + _, err = s.client.GetNode(s.Context(), defaults.Namespace, server.GetName()) + if trace.IsNotFound(err) { + return false + } + require.NoError(s.T(), err) + return true + }, 5*time.Second, time.Second) + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + Steps: []resource.TestStep{ + { + Config: s.terraformConfig + "\n" + `resource "` + r + `" "` + id + `" { }`, + ResourceName: name, + ImportState: true, + ImportStateId: id, + ImportStateCheck: func(state []*terraform.InstanceState) error { + require.Equal(s.T(), state[0].Attributes["kind"], types.KindNode) + require.Equal(s.T(), state[0].Attributes["sub_kind"], types.SubKindOpenSSHEICENode) + require.Equal(s.T(), state[0].Attributes["spec.addr"], "127.0.0.1:22") + require.Equal(s.T(), state[0].Attributes["spec.hostname"], "foobar") + require.Equal(s.T(), state[0].Attributes["spec.cloud_metadata.aws.account_id"], "123") + require.Equal(s.T(), state[0].Attributes["spec.cloud_metadata.aws.instance_id"], "123") + require.Equal(s.T(), state[0].Attributes["spec.cloud_metadata.aws.region"], "us-east-1") + require.Equal(s.T(), state[0].Attributes["spec.cloud_metadata.aws.vpc_id"], "123") + require.Equal(s.T(), state[0].Attributes["spec.cloud_metadata.aws.integration"], "foo") + require.Equal(s.T(), state[0].Attributes["spec.cloud_metadata.aws.subnet_id"], "123") + return nil + }, + }, + }, + }) +} From aa7deb0f207510ec83e0263babdfc3824a4d4087 Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Thu, 22 Feb 2024 16:27:54 -0500 Subject: [PATCH 06/11] lint --- .../test/fixtures/server_openssh_0_create.tf | 18 +++++----- .../test/fixtures/server_openssh_1_update.tf | 18 +++++----- .../fixtures/server_openssheice_0_create.tf | 36 +++++++++---------- .../fixtures/server_openssheice_1_update.tf | 36 +++++++++---------- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/terraform/test/fixtures/server_openssh_0_create.tf b/terraform/test/fixtures/server_openssh_0_create.tf index ff699594f..459c6bdc3 100644 --- a/terraform/test/fixtures/server_openssh_0_create.tf +++ b/terraform/test/fixtures/server_openssh_0_create.tf @@ -1,11 +1,11 @@ resource "teleport_server" "test" { - version = "v2" - sub_kind = "openssh" - metadata = { - name = "test" - } - spec = { - addr = "127.0.0.1:22" - hostname = "test.local" - } + version = "v2" + sub_kind = "openssh" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + } } diff --git a/terraform/test/fixtures/server_openssh_1_update.tf b/terraform/test/fixtures/server_openssh_1_update.tf index 2a2071a9e..a92143d5b 100644 --- a/terraform/test/fixtures/server_openssh_1_update.tf +++ b/terraform/test/fixtures/server_openssh_1_update.tf @@ -1,11 +1,11 @@ resource "teleport_server" "test" { - version = "v2" - sub_kind = "openssh" - metadata = { - name = "test" - } - spec = { - addr = "127.0.0.1:23" - hostname = "test.local" - } + version = "v2" + sub_kind = "openssh" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:23" + hostname = "test.local" + } } diff --git a/terraform/test/fixtures/server_openssheice_0_create.tf b/terraform/test/fixtures/server_openssheice_0_create.tf index 9d0dc4e59..37828070b 100644 --- a/terraform/test/fixtures/server_openssheice_0_create.tf +++ b/terraform/test/fixtures/server_openssheice_0_create.tf @@ -1,21 +1,21 @@ resource "teleport_server" "test" { - version = "v2" - sub_kind = "openssh-ec2-ice" - metadata = { - name = "test" - } - spec = { - addr = "127.0.0.1:22" - hostname = "test.local" - cloud_metadata = { - aws = { - account_id = "123" - instance_id = "123" - region = "us-east-1" - vpc_id = "123" - integration = "foo" - subnet_id = "123" - } - } + version = "v2" + sub_kind = "openssh-ec2-ice" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + cloud_metadata = { + aws = { + account_id = "123" + instance_id = "123" + region = "us-east-1" + vpc_id = "123" + integration = "foo" + subnet_id = "123" + } } + } } diff --git a/terraform/test/fixtures/server_openssheice_1_update.tf b/terraform/test/fixtures/server_openssheice_1_update.tf index 263072d6a..100d25364 100644 --- a/terraform/test/fixtures/server_openssheice_1_update.tf +++ b/terraform/test/fixtures/server_openssheice_1_update.tf @@ -1,21 +1,21 @@ resource "teleport_server" "test" { - version = "v2" - sub_kind = "openssh-ec2-ice" - metadata = { - name = "test" - } - spec = { - addr = "127.0.0.1:23" - hostname = "test.local" - cloud_metadata = { - aws = { - account_id = "123" - instance_id = "123" - region = "us-east-1" - vpc_id = "123" - integration = "foo" - subnet_id = "123" - } - } + version = "v2" + sub_kind = "openssh-ec2-ice" + metadata = { + name = "test" + } + spec = { + addr = "127.0.0.1:23" + hostname = "test.local" + cloud_metadata = { + aws = { + account_id = "123" + instance_id = "123" + region = "us-east-1" + vpc_id = "123" + integration = "foo" + subnet_id = "123" + } } + } } From ce5bdaace539fcff9da5855d2e937f00f2f852ed Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Tue, 12 Mar 2024 12:29:53 -0400 Subject: [PATCH 07/11] Add support and test for dynamic name for OpensSSH servers --- terraform/Makefile | 2 +- terraform/example/server.tf.example | 10 ++-- terraform/gen/plural_resource.go.tpl | 25 +++++----- terraform/gen/singular_resource.go.tpl | 2 +- terraform/protoc-gen-terraform-teleport.yaml | 19 ++++++- .../provider/resource_teleport_access_list.go | 13 ++--- terraform/provider/resource_teleport_app.go | 13 ++--- .../resource_teleport_auth_preference.go | 2 +- ...rce_teleport_cluster_maintenance_config.go | 2 +- ...urce_teleport_cluster_networking_config.go | 2 +- .../provider/resource_teleport_database.go | 13 ++--- .../resource_teleport_device_trust.go | 5 +- .../resource_teleport_github_connector.go | 13 ++--- .../provider/resource_teleport_login_rule.go | 5 +- .../resource_teleport_oidc_connector.go | 13 ++--- .../resource_teleport_okta_import_rule.go | 13 ++--- .../resource_teleport_provision_token.go | 13 ++--- terraform/provider/resource_teleport_role.go | 13 ++--- .../resource_teleport_saml_connector.go | 13 ++--- .../provider/resource_teleport_server.go | 28 ++++++----- ...ource_teleport_session_recording_config.go | 2 +- .../resource_teleport_trusted_cluster.go | 13 ++--- terraform/provider/resource_teleport_user.go | 13 ++--- terraform/reference.mdx | 12 +++-- .../server_openssh_nameless_0_create.tf | 8 +++ .../server_openssh_nameless_1_update.tf | 8 +++ terraform/test/server_test.go | 49 +++++++++++++++++++ terraform/tfschema/types_terraform.go | 11 +++-- 28 files changed, 222 insertions(+), 113 deletions(-) create mode 100644 terraform/test/fixtures/server_openssh_nameless_0_create.tf create mode 100644 terraform/test/fixtures/server_openssh_nameless_1_update.tf diff --git a/terraform/Makefile b/terraform/Makefile index f9940f214..485ba051f 100644 --- a/terraform/Makefile +++ b/terraform/Makefile @@ -117,7 +117,7 @@ ifeq ($(shell expr $(CURRENT_ULIMIT) \< 1024), 1) @echo "ulimit -n is too low ($(CURRENT_ULIMIT)), please set ulimit -n 1024" @exit -1 endif - TF_ACC=true go test ./test -v + TF_ACC=true go test ./test -v # -run '^\QTestTerraform\E$$/^\QTestNamelessOpenSSHServer\E$$' .PHONY: apply apply: install diff --git a/terraform/example/server.tf.example b/terraform/example/server.tf.example index b908d0ad0..dd847f42a 100644 --- a/terraform/example/server.tf.example +++ b/terraform/example/server.tf.example @@ -1,9 +1,13 @@ resource "teleport_server" "ssh_agentless" { version = "v2" sub_kind = "openssh" - metadata = { - name = "test" - } + // Name is not required for servers, this is a special case. + // When a name is not set, an UUID will be generated by Teleport and + // imported back into Terraform. + // Giving unique IDs to servers allows UUID-based dialing (as opposed to + // host-based dialing and IP-based dialing) which is more robust than its + // counterparts as it can point to a specific server if multiple servers + // share the same hostname/ip. spec = { addr = "127.0.0.1:22" hostname = "test.local" diff --git a/terraform/gen/plural_resource.go.tpl b/terraform/gen/plural_resource.go.tpl index 1dcb78bdf..ade1c7760 100644 --- a/terraform/gen/plural_resource.go.tpl +++ b/terraform/gen/plural_resource.go.tpl @@ -119,6 +119,19 @@ func (r resourceTeleport{{.Name}}) Create(ctx context.Context, req tfsdk.CreateR {{- else }} {{.VarName}}Resource := {{.VarName}} {{end}} + +{{- if .ForceSetKind }} + {{.VarName}}Resource.Kind = {{.ForceSetKind}} +{{- end}} + +{{if .HasCheckAndSetDefaults -}} + err = {{.VarName}}Resource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting {{.Name}} defaults", trace.Wrap(err), "{{.Kind}}")) + return + } +{{- end}} + id := {{.VarName}}Resource.Metadata.Name _, err = r.p.Client.{{.GetMethod}}(ctx, {{if .Namespaced}}defaults.Namespace, {{end}}id{{if ne .WithSecrets ""}}, {{.WithSecrets}}{{end}}) @@ -135,18 +148,6 @@ func (r resourceTeleport{{.Name}}) Create(ctx context.Context, req tfsdk.CreateR return } - {{- if .ForceSetKind }} - {{.VarName}}Resource.Kind = {{.ForceSetKind}} - {{- end}} - - {{if .HasCheckAndSetDefaults -}} - err = {{.VarName}}Resource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting {{.Name}} defaults", trace.Wrap(err), "{{.Kind}}")) - return - } - {{- end}} - {{if eq .UpsertMethodArity 2}}_, {{end}}err = r.p.Client.{{.CreateMethod}}(ctx, {{.VarName}}Resource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating {{.Name}}", trace.Wrap(err), "{{.Kind}}")) diff --git a/terraform/gen/singular_resource.go.tpl b/terraform/gen/singular_resource.go.tpl index 6e0b1396b..39145d3cd 100644 --- a/terraform/gen/singular_resource.go.tpl +++ b/terraform/gen/singular_resource.go.tpl @@ -55,7 +55,7 @@ func (r resourceTeleport{{.Name}}Type) NewResource(_ context.Context, p tfsdk.Pr }, nil } -// Create creates the provision token +// Create creates the {{.Name}} func (r resourceTeleport{{.Name}}) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { if !r.p.IsConfigured(resp.Diagnostics) { return diff --git a/terraform/protoc-gen-terraform-teleport.yaml b/terraform/protoc-gen-terraform-teleport.yaml index 6bbdcc1ee..9dc836876 100644 --- a/terraform/protoc-gen-terraform-teleport.yaml +++ b/terraform/protoc-gen-terraform-teleport.yaml @@ -254,8 +254,21 @@ computed_fields: # Server - "ServerV2.Kind" + # Name is not required for servers, this is a special case. + # When a name is not set, an UUID will be generated by Teleport and + # imported back into Terraform. + # Giving unique IDs to servers allows UUID-based dialing (as opposed to + # host-based dialing and IP-based dialing) which is more robust than its + # counterparts as it can point to a specific server if multiple servers + # share the same hostname/ip. + - "ServerV2.Metadata.Name" + # Metadata must be marked computed as well, because we ccan potentially compute Metadata.Name. + # If there's no metadata and the attribute is not marked as comnputed, it will + # keep its values.Null = true field, which means its content won't be imported + # back into the state. + - "ServerV2.Metadata" - # Session recording + # Session recording - "SessionRecordingConfigV2.Spec.Mode" - "SessionRecordingConfigV2.Kind" @@ -316,7 +329,6 @@ required_fields: - "SAMLConnectorV2.Version" # Server - - "ServerV2.Metadata.Name" - "ServerV2.Version" - "ServerV2.SubKind" @@ -359,6 +371,9 @@ plan_modifiers: ProvisionTokenV2.Metadata.Name: - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" + ServerV2.Metadata.Name: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" # Version MUST NOT change. Due to the way Terraform imports back the resource # in its state (the provider relies on `USeStateForUnknown`) and the fact diff --git a/terraform/provider/resource_teleport_access_list.go b/terraform/provider/resource_teleport_access_list.go index 256fbe9a5..bdff88c57 100755 --- a/terraform/provider/resource_teleport_access_list.go +++ b/terraform/provider/resource_teleport_access_list.go @@ -81,6 +81,13 @@ func (r resourceTeleportAccessList) Create(ctx context.Context, req tfsdk.Create resp.Diagnostics.Append(diagFromWrappedErr("Error reading AccessList", trace.Errorf("Can not convert %T to AccessList: %s", accessListResource, err), "access_list")) return } + +err = accessListResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting AccessList defaults", trace.Wrap(err), "access_list")) + return + } + id := accessListResource.Metadata.Name _, err = r.p.Client.AccessListClient().GetAccessList(ctx, id) @@ -97,12 +104,6 @@ func (r resourceTeleportAccessList) Create(ctx context.Context, req tfsdk.Create return } - err = accessListResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting AccessList defaults", trace.Wrap(err), "access_list")) - return - } - _, err = r.p.Client.AccessListClient().UpsertAccessList(ctx, accessListResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating AccessList", trace.Wrap(err), "access_list")) diff --git a/terraform/provider/resource_teleport_app.go b/terraform/provider/resource_teleport_app.go index e4dee092c..f7e85b169 100755 --- a/terraform/provider/resource_teleport_app.go +++ b/terraform/provider/resource_teleport_app.go @@ -78,6 +78,13 @@ func (r resourceTeleportApp) Create(ctx context.Context, req tfsdk.CreateResourc appResource := app + +err = appResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting App defaults", trace.Wrap(err), "app")) + return + } + id := appResource.Metadata.Name _, err = r.p.Client.GetApp(ctx, id) @@ -94,12 +101,6 @@ func (r resourceTeleportApp) Create(ctx context.Context, req tfsdk.CreateResourc return } - err = appResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting App defaults", trace.Wrap(err), "app")) - return - } - err = r.p.Client.CreateApp(ctx, appResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating App", trace.Wrap(err), "app")) diff --git a/terraform/provider/resource_teleport_auth_preference.go b/terraform/provider/resource_teleport_auth_preference.go index 063ae88d6..bfec82e2a 100755 --- a/terraform/provider/resource_teleport_auth_preference.go +++ b/terraform/provider/resource_teleport_auth_preference.go @@ -52,7 +52,7 @@ func (r resourceTeleportAuthPreferenceType) NewResource(_ context.Context, p tfs }, nil } -// Create creates the provision token +// Create creates the AuthPreference func (r resourceTeleportAuthPreference) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { if !r.p.IsConfigured(resp.Diagnostics) { return diff --git a/terraform/provider/resource_teleport_cluster_maintenance_config.go b/terraform/provider/resource_teleport_cluster_maintenance_config.go index 45147cc48..2e0cefa9e 100755 --- a/terraform/provider/resource_teleport_cluster_maintenance_config.go +++ b/terraform/provider/resource_teleport_cluster_maintenance_config.go @@ -53,7 +53,7 @@ func (r resourceTeleportClusterMaintenanceConfigType) NewResource(_ context.Cont }, nil } -// Create creates the provision token +// Create creates the ClusterMaintenanceConfig func (r resourceTeleportClusterMaintenanceConfig) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { if !r.p.IsConfigured(resp.Diagnostics) { return diff --git a/terraform/provider/resource_teleport_cluster_networking_config.go b/terraform/provider/resource_teleport_cluster_networking_config.go index 1bc58b4b2..fde906846 100755 --- a/terraform/provider/resource_teleport_cluster_networking_config.go +++ b/terraform/provider/resource_teleport_cluster_networking_config.go @@ -52,7 +52,7 @@ func (r resourceTeleportClusterNetworkingConfigType) NewResource(_ context.Conte }, nil } -// Create creates the provision token +// Create creates the ClusterNetworkingConfig func (r resourceTeleportClusterNetworkingConfig) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { if !r.p.IsConfigured(resp.Diagnostics) { return diff --git a/terraform/provider/resource_teleport_database.go b/terraform/provider/resource_teleport_database.go index 6b7862d08..d1ff590d2 100755 --- a/terraform/provider/resource_teleport_database.go +++ b/terraform/provider/resource_teleport_database.go @@ -78,6 +78,13 @@ func (r resourceTeleportDatabase) Create(ctx context.Context, req tfsdk.CreateRe databaseResource := database + +err = databaseResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting Database defaults", trace.Wrap(err), "db")) + return + } + id := databaseResource.Metadata.Name _, err = r.p.Client.GetDatabase(ctx, id) @@ -94,12 +101,6 @@ func (r resourceTeleportDatabase) Create(ctx context.Context, req tfsdk.CreateRe return } - err = databaseResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting Database defaults", trace.Wrap(err), "db")) - return - } - err = r.p.Client.CreateDatabase(ctx, databaseResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating Database", trace.Wrap(err), "db")) diff --git a/terraform/provider/resource_teleport_device_trust.go b/terraform/provider/resource_teleport_device_trust.go index c73ca28cc..e1f6462db 100755 --- a/terraform/provider/resource_teleport_device_trust.go +++ b/terraform/provider/resource_teleport_device_trust.go @@ -82,6 +82,9 @@ func (r resourceTeleportDeviceV1) Create(ctx context.Context, req tfsdk.CreateRe trustedDeviceResource := trustedDevice + + + id := trustedDeviceResource.Metadata.Name _, err = r.p.Client.GetDeviceResource(ctx, id) @@ -98,8 +101,6 @@ func (r resourceTeleportDeviceV1) Create(ctx context.Context, req tfsdk.CreateRe return } - - _, err = r.p.Client.UpsertDeviceResource(ctx, trustedDeviceResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating DeviceV1", trace.Wrap(err), "device")) diff --git a/terraform/provider/resource_teleport_github_connector.go b/terraform/provider/resource_teleport_github_connector.go index 236b37404..715ff8a3c 100755 --- a/terraform/provider/resource_teleport_github_connector.go +++ b/terraform/provider/resource_teleport_github_connector.go @@ -78,6 +78,13 @@ func (r resourceTeleportGithubConnector) Create(ctx context.Context, req tfsdk.C githubConnectorResource := githubConnector + +err = githubConnectorResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting GithubConnector defaults", trace.Wrap(err), "github")) + return + } + id := githubConnectorResource.Metadata.Name _, err = r.p.Client.GetGithubConnector(ctx, id, true) @@ -94,12 +101,6 @@ func (r resourceTeleportGithubConnector) Create(ctx context.Context, req tfsdk.C return } - err = githubConnectorResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting GithubConnector defaults", trace.Wrap(err), "github")) - return - } - _, err = r.p.Client.CreateGithubConnector(ctx, githubConnectorResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating GithubConnector", trace.Wrap(err), "github")) diff --git a/terraform/provider/resource_teleport_login_rule.go b/terraform/provider/resource_teleport_login_rule.go index f32071474..58b32585c 100755 --- a/terraform/provider/resource_teleport_login_rule.go +++ b/terraform/provider/resource_teleport_login_rule.go @@ -78,6 +78,9 @@ func (r resourceTeleportLoginRule) Create(ctx context.Context, req tfsdk.CreateR loginRuleResource := loginRule + + + id := loginRuleResource.Metadata.Name _, err = r.p.Client.GetLoginRule(ctx, id) @@ -94,8 +97,6 @@ func (r resourceTeleportLoginRule) Create(ctx context.Context, req tfsdk.CreateR return } - - _, err = r.p.Client.UpsertLoginRule(ctx, loginRuleResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating LoginRule", trace.Wrap(err), "login_rule")) diff --git a/terraform/provider/resource_teleport_oidc_connector.go b/terraform/provider/resource_teleport_oidc_connector.go index 7e5b54daa..f989d5d0e 100755 --- a/terraform/provider/resource_teleport_oidc_connector.go +++ b/terraform/provider/resource_teleport_oidc_connector.go @@ -78,6 +78,13 @@ func (r resourceTeleportOIDCConnector) Create(ctx context.Context, req tfsdk.Cre oidcConnectorResource := oidcConnector + +err = oidcConnectorResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting OIDCConnector defaults", trace.Wrap(err), "oidc")) + return + } + id := oidcConnectorResource.Metadata.Name _, err = r.p.Client.GetOIDCConnector(ctx, id, true) @@ -94,12 +101,6 @@ func (r resourceTeleportOIDCConnector) Create(ctx context.Context, req tfsdk.Cre return } - err = oidcConnectorResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting OIDCConnector defaults", trace.Wrap(err), "oidc")) - return - } - _, err = r.p.Client.CreateOIDCConnector(ctx, oidcConnectorResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating OIDCConnector", trace.Wrap(err), "oidc")) diff --git a/terraform/provider/resource_teleport_okta_import_rule.go b/terraform/provider/resource_teleport_okta_import_rule.go index c39fc8c80..09e383b35 100755 --- a/terraform/provider/resource_teleport_okta_import_rule.go +++ b/terraform/provider/resource_teleport_okta_import_rule.go @@ -78,6 +78,13 @@ func (r resourceTeleportOktaImportRule) Create(ctx context.Context, req tfsdk.Cr oktaImportRuleResource := oktaImportRule + +err = oktaImportRuleResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting OktaImportRule defaults", trace.Wrap(err), "okta_import_rule")) + return + } + id := oktaImportRuleResource.Metadata.Name _, err = r.p.Client.OktaClient().GetOktaImportRule(ctx, id) @@ -94,12 +101,6 @@ func (r resourceTeleportOktaImportRule) Create(ctx context.Context, req tfsdk.Cr return } - err = oktaImportRuleResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting OktaImportRule defaults", trace.Wrap(err), "okta_import_rule")) - return - } - _, err = r.p.Client.OktaClient().CreateOktaImportRule(ctx, oktaImportRuleResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating OktaImportRule", trace.Wrap(err), "okta_import_rule")) diff --git a/terraform/provider/resource_teleport_provision_token.go b/terraform/provider/resource_teleport_provision_token.go index a360104e8..d0052350f 100755 --- a/terraform/provider/resource_teleport_provision_token.go +++ b/terraform/provider/resource_teleport_provision_token.go @@ -90,6 +90,13 @@ func (r resourceTeleportProvisionToken) Create(ctx context.Context, req tfsdk.Cr provisionTokenResource := provisionToken + +err = provisionTokenResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting ProvisionToken defaults", trace.Wrap(err), "token")) + return + } + id := provisionTokenResource.Metadata.Name _, err = r.p.Client.GetToken(ctx, id) @@ -106,12 +113,6 @@ func (r resourceTeleportProvisionToken) Create(ctx context.Context, req tfsdk.Cr return } - err = provisionTokenResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting ProvisionToken defaults", trace.Wrap(err), "token")) - return - } - err = r.p.Client.UpsertToken(ctx, provisionTokenResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating ProvisionToken", trace.Wrap(err), "token")) diff --git a/terraform/provider/resource_teleport_role.go b/terraform/provider/resource_teleport_role.go index e2388fd3d..ecf52dd45 100755 --- a/terraform/provider/resource_teleport_role.go +++ b/terraform/provider/resource_teleport_role.go @@ -78,6 +78,13 @@ func (r resourceTeleportRole) Create(ctx context.Context, req tfsdk.CreateResour roleResource := role + +err = roleResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting Role defaults", trace.Wrap(err), "role")) + return + } + id := roleResource.Metadata.Name _, err = r.p.Client.GetRole(ctx, id) @@ -94,12 +101,6 @@ func (r resourceTeleportRole) Create(ctx context.Context, req tfsdk.CreateResour return } - err = roleResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting Role defaults", trace.Wrap(err), "role")) - return - } - _, err = r.p.Client.CreateRole(ctx, roleResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating Role", trace.Wrap(err), "role")) diff --git a/terraform/provider/resource_teleport_saml_connector.go b/terraform/provider/resource_teleport_saml_connector.go index b26666df4..03a933749 100755 --- a/terraform/provider/resource_teleport_saml_connector.go +++ b/terraform/provider/resource_teleport_saml_connector.go @@ -78,6 +78,13 @@ func (r resourceTeleportSAMLConnector) Create(ctx context.Context, req tfsdk.Cre samlConnectorResource := samlConnector + +err = samlConnectorResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting SAMLConnector defaults", trace.Wrap(err), "saml")) + return + } + id := samlConnectorResource.Metadata.Name _, err = r.p.Client.GetSAMLConnector(ctx, id, true) @@ -94,12 +101,6 @@ func (r resourceTeleportSAMLConnector) Create(ctx context.Context, req tfsdk.Cre return } - err = samlConnectorResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting SAMLConnector defaults", trace.Wrap(err), "saml")) - return - } - _, err = r.p.Client.CreateSAMLConnector(ctx, samlConnectorResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating SAMLConnector", trace.Wrap(err), "saml")) diff --git a/terraform/provider/resource_teleport_server.go b/terraform/provider/resource_teleport_server.go index f39632683..ef8d9d371 100755 --- a/terraform/provider/resource_teleport_server.go +++ b/terraform/provider/resource_teleport_server.go @@ -22,8 +22,7 @@ import ( "fmt" apitypes "github.com/gravitational/teleport/api/types" - - "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/integrations/lib/backoff" "github.com/gravitational/trace" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -31,6 +30,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/jonboulle/clockwork" + "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport-plugins/terraform/tfschema" ) @@ -76,8 +76,17 @@ func (r resourceTeleportServer) Create(ctx context.Context, req tfsdk.CreateReso return } + serverResource := server + serverResource.Kind = apitypes.KindNode + +err = serverResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting Server defaults", trace.Wrap(err), "node")) + return + } + id := serverResource.Metadata.Name _, err = r.p.Client.GetNode(ctx, defaults.Namespace, id) @@ -93,20 +102,13 @@ func (r resourceTeleportServer) Create(ctx context.Context, req tfsdk.CreateReso resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) return } - serverResource.Kind = apitypes.KindNode - - err = serverResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting Server defaults", trace.Wrap(err), "node")) - return - } _, err = r.p.Client.UpsertNode(ctx, serverResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating Server", trace.Wrap(err), "node")) return } - + // Not really an inferface, just using the same name for easier templating. var serverI apitypes.Server tries := 0 @@ -182,7 +184,7 @@ func (r resourceTeleportServer) Read(ctx context.Context, req tfsdk.ReadResource resp.Diagnostics.Append(diagFromWrappedErr("Error reading Server", trace.Wrap(err), "node")) return } - + server := serverI.(*apitypes.ServerV2) diags = tfschema.CopyServerV2ToTerraform(ctx, server, &state) resp.Diagnostics.Append(diags...) @@ -219,6 +221,7 @@ func (r resourceTeleportServer) Update(ctx context.Context, req tfsdk.UpdateReso } serverResource := server + if err := serverResource.CheckAndSetDefaults(); err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error updating Server", err, "node")) return @@ -236,7 +239,7 @@ func (r resourceTeleportServer) Update(ctx context.Context, req tfsdk.UpdateReso resp.Diagnostics.Append(diagFromWrappedErr("Error updating Server", err, "node")) return } - + // Not really an inferface, just using the same name for easier templating. var serverI apitypes.Server @@ -308,6 +311,7 @@ func (r resourceTeleportServer) ImportState(ctx context.Context, req tfsdk.Impor return } + serverResource := server.(*apitypes.ServerV2) var state types.Object diff --git a/terraform/provider/resource_teleport_session_recording_config.go b/terraform/provider/resource_teleport_session_recording_config.go index c4718a97a..a8f4e883a 100755 --- a/terraform/provider/resource_teleport_session_recording_config.go +++ b/terraform/provider/resource_teleport_session_recording_config.go @@ -52,7 +52,7 @@ func (r resourceTeleportSessionRecordingConfigType) NewResource(_ context.Contex }, nil } -// Create creates the provision token +// Create creates the SessionRecordingConfig func (r resourceTeleportSessionRecordingConfig) Create(ctx context.Context, req tfsdk.CreateResourceRequest, resp *tfsdk.CreateResourceResponse) { if !r.p.IsConfigured(resp.Diagnostics) { return diff --git a/terraform/provider/resource_teleport_trusted_cluster.go b/terraform/provider/resource_teleport_trusted_cluster.go index 1312b98b4..6d93a2907 100755 --- a/terraform/provider/resource_teleport_trusted_cluster.go +++ b/terraform/provider/resource_teleport_trusted_cluster.go @@ -78,6 +78,13 @@ func (r resourceTeleportTrustedCluster) Create(ctx context.Context, req tfsdk.Cr trustedClusterResource := trustedCluster + +err = trustedClusterResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting TrustedCluster defaults", trace.Wrap(err), "trusted_cluster")) + return + } + id := trustedClusterResource.Metadata.Name _, err = r.p.Client.GetTrustedCluster(ctx, id) @@ -94,12 +101,6 @@ func (r resourceTeleportTrustedCluster) Create(ctx context.Context, req tfsdk.Cr return } - err = trustedClusterResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting TrustedCluster defaults", trace.Wrap(err), "trusted_cluster")) - return - } - _, err = r.p.Client.UpsertTrustedCluster(ctx, trustedClusterResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating TrustedCluster", trace.Wrap(err), "trusted_cluster")) diff --git a/terraform/provider/resource_teleport_user.go b/terraform/provider/resource_teleport_user.go index 92a1940a9..d5448d820 100755 --- a/terraform/provider/resource_teleport_user.go +++ b/terraform/provider/resource_teleport_user.go @@ -78,6 +78,13 @@ func (r resourceTeleportUser) Create(ctx context.Context, req tfsdk.CreateResour userResource := user + +err = userResource.CheckAndSetDefaults() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error setting User defaults", trace.Wrap(err), "user")) + return + } + id := userResource.Metadata.Name _, err = r.p.Client.GetUser(ctx, id, false) @@ -94,12 +101,6 @@ func (r resourceTeleportUser) Create(ctx context.Context, req tfsdk.CreateResour return } - err = userResource.CheckAndSetDefaults() - if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting User defaults", trace.Wrap(err), "user")) - return - } - _, err = r.p.Client.CreateUser(ctx, userResource) if err != nil { resp.Diagnostics.Append(diagFromWrappedErr("Error creating User", trace.Wrap(err), "user")) diff --git a/terraform/reference.mdx b/terraform/reference.mdx index 944ec5e7c..4db0d8f78 100755 --- a/terraform/reference.mdx +++ b/terraform/reference.mdx @@ -2208,7 +2208,7 @@ Metadata is resource metadata | description | string | | Description is object description | | expires | RFC3339 time | | Expires is a global expiry time header can be set on any resource in the system. | | labels | map of strings | | Labels is a set of labels | -| name | string | * | Name is an object name | +| name | string | | Name is an object name | | namespace | string | | Namespace is object namespace. The field should be called "namespace" when it returns in Teleport 2.4. | | revision | string | | Revision is an opaque identifier which tracks the versions of a resource over time. Clients should ignore and not alter its value but must return the revision in any updates of a resource. | @@ -2280,9 +2280,13 @@ Example: resource "teleport_server" "ssh_agentless" { version = "v2" sub_kind = "openssh" - metadata = { - name = "test" - } + // Name is not required for servers, this is a special case. + // When a name is not set, an UUID will be generated by Teleport and + // imported back into Terraform. + // Giving unique IDs to servers allows UUID-based dialing (as opposed to + // host-based dialing and IP-based dialing) which is more robust than its + // counterparts as it can point to a specific server if multiple servers + // share the same hostname/ip. spec = { addr = "127.0.0.1:22" hostname = "test.local" diff --git a/terraform/test/fixtures/server_openssh_nameless_0_create.tf b/terraform/test/fixtures/server_openssh_nameless_0_create.tf new file mode 100644 index 000000000..e0865e037 --- /dev/null +++ b/terraform/test/fixtures/server_openssh_nameless_0_create.tf @@ -0,0 +1,8 @@ +resource "teleport_server" "test" { + version = "v2" + sub_kind = "openssh" + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + } +} diff --git a/terraform/test/fixtures/server_openssh_nameless_1_update.tf b/terraform/test/fixtures/server_openssh_nameless_1_update.tf new file mode 100644 index 000000000..d50a42813 --- /dev/null +++ b/terraform/test/fixtures/server_openssh_nameless_1_update.tf @@ -0,0 +1,8 @@ +resource "teleport_server" "test" { + version = "v2" + sub_kind = "openssh" + spec = { + addr = "127.0.0.1:23" + hostname = "test.local" + } +} diff --git a/terraform/test/server_test.go b/terraform/test/server_test.go index eb0014192..56b70e394 100644 --- a/terraform/test/server_test.go +++ b/terraform/test/server_test.go @@ -74,6 +74,55 @@ func (s *TerraformSuite) TestOpenSSHServer() { }) } +func (s *TerraformSuite) TestOpenSSHServerNameless() { + checkServerDestroyed := func(state *terraform.State) error { + // The name is a UUID but we can lookup by hostname as well. + _, err := s.client.GetNode(s.Context(), defaults.Namespace, "test.local") + if trace.IsNotFound(err) { + return nil + } + + return err + } + + name := "teleport_server.test" + + resource.Test(s.T(), resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkServerDestroyed, + Steps: []resource.TestStep{ + { + Config: s.getFixture("server_openssh_nameless_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", types.KindNode), + resource.TestCheckResourceAttr(name, "sub_kind", types.SubKindOpenSSHNode), + resource.TestCheckResourceAttr(name, "version", "v2"), + resource.TestCheckResourceAttr(name, "spec.addr", "127.0.0.1:22"), + resource.TestCheckResourceAttr(name, "spec.hostname", "test.local"), + ), + }, + { + Config: s.getFixture("server_openssh_nameless_0_create.tf"), + PlanOnly: true, + }, + { + Config: s.getFixture("server_openssh_nameless_1_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", types.KindNode), + resource.TestCheckResourceAttr(name, "sub_kind", types.SubKindOpenSSHNode), + resource.TestCheckResourceAttr(name, "version", "v2"), + resource.TestCheckResourceAttr(name, "spec.addr", "127.0.0.1:23"), + resource.TestCheckResourceAttr(name, "spec.hostname", "test.local"), + ), + }, + { + Config: s.getFixture("server_openssh_nameless_1_update.tf"), + PlanOnly: true, + }, + }, + }) +} + func (s *TerraformSuite) TestImportOpenSSHServer() { r := "teleport_server" id := "test_import" diff --git a/terraform/tfschema/types_terraform.go b/terraform/tfschema/types_terraform.go index 0bc80088d..76c823d1f 100644 --- a/terraform/tfschema/types_terraform.go +++ b/terraform/tfschema/types_terraform.go @@ -552,9 +552,10 @@ func GenSchemaServerV2(ctx context.Context) (github_com_hashicorp_terraform_plug Type: github_com_hashicorp_terraform_plugin_framework_types.MapType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, }, "name": { + Computed: true, Description: "Name is an object name", - PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()}, - Required: true, + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown(), github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()}, Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, }, "namespace": { @@ -570,8 +571,10 @@ func GenSchemaServerV2(ctx context.Context) (github_com_hashicorp_terraform_plug Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, }, }), - Description: "Metadata is resource metadata", - Optional: true, + Computed: true, + Description: "Metadata is resource metadata", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, }, "spec": { Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ From b7656069603aa459c7975ab59b7138f82c1bb46b Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Wed, 13 Mar 2024 11:14:07 -0400 Subject: [PATCH 08/11] Update terraform/test/server_test.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marco André Dinis --- terraform/test/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/test/server_test.go b/terraform/test/server_test.go index 56b70e394..322ffc16e 100644 --- a/terraform/test/server_test.go +++ b/terraform/test/server_test.go @@ -1,5 +1,5 @@ /* -Copyright 2015-2021 Gravitational, Inc. +Copyright 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. From b7145dc9e5068aba0bbbe1b3193c68488ebe1085 Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Wed, 13 Mar 2024 12:54:25 -0400 Subject: [PATCH 09/11] address feedback --- terraform/Makefile | 2 +- terraform/example/server.tf.example | 52 ++++++++++++++----- terraform/gen/main.go | 4 +- terraform/gen/plural_resource.go.tpl | 6 +-- terraform/protoc-gen-terraform-teleport.yaml | 2 +- .../provider/resource_teleport_access_list.go | 7 ++- terraform/provider/resource_teleport_app.go | 7 ++- .../provider/resource_teleport_database.go | 7 ++- .../resource_teleport_device_trust.go | 2 - .../resource_teleport_github_connector.go | 7 ++- .../provider/resource_teleport_login_rule.go | 2 - .../resource_teleport_oidc_connector.go | 7 ++- .../resource_teleport_okta_import_rule.go | 7 ++- .../resource_teleport_provision_token.go | 7 ++- terraform/provider/resource_teleport_role.go | 7 ++- .../resource_teleport_saml_connector.go | 7 ++- .../provider/resource_teleport_server.go | 7 ++- .../resource_teleport_trusted_cluster.go | 7 ++- terraform/provider/resource_teleport_user.go | 7 ++- terraform/test/server_test.go | 3 +- terraform/tfschema/types_terraform.go | 2 +- terraform/tfschema/validators.go | 5 +- 22 files changed, 88 insertions(+), 76 deletions(-) diff --git a/terraform/Makefile b/terraform/Makefile index 485ba051f..f9940f214 100644 --- a/terraform/Makefile +++ b/terraform/Makefile @@ -117,7 +117,7 @@ ifeq ($(shell expr $(CURRENT_ULIMIT) \< 1024), 1) @echo "ulimit -n is too low ($(CURRENT_ULIMIT)), please set ulimit -n 1024" @exit -1 endif - TF_ACC=true go test ./test -v # -run '^\QTestTerraform\E$$/^\QTestNamelessOpenSSHServer\E$$' + TF_ACC=true go test ./test -v .PHONY: apply apply: install diff --git a/terraform/example/server.tf.example b/terraform/example/server.tf.example index dd847f42a..103a076e0 100644 --- a/terraform/example/server.tf.example +++ b/terraform/example/server.tf.example @@ -1,15 +1,43 @@ resource "teleport_server" "ssh_agentless" { - version = "v2" - sub_kind = "openssh" - // Name is not required for servers, this is a special case. - // When a name is not set, an UUID will be generated by Teleport and - // imported back into Terraform. - // Giving unique IDs to servers allows UUID-based dialing (as opposed to - // host-based dialing and IP-based dialing) which is more robust than its - // counterparts as it can point to a specific server if multiple servers - // share the same hostname/ip. - spec = { - addr = "127.0.0.1:22" - hostname = "test.local" + version = "v2" + sub_kind = "openssh" + // Name is not required for servers, this is a special case. + // When a name is not set, an UUID will be generated by Teleport and + // imported back into Terraform. + // Giving unique IDs to servers allows UUID-based dialing (as opposed to + // host-based dialing and IP-based dialing) which is more robust than its + // counterparts as it can point to a specific server if multiple servers + // share the same hostname/ip. + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + } +} + +resource "teleport_server" "ssh_agentless_eice" { + version = "v2" + sub_kind = "openssh-ec2-ice" + metadata = { + // It is recommended to put the instance ID as a name for EC2 Instance Connect + // When dialing to this instance, teleport will detect that this is an + // AWS instance ID an will contact this specific instance. This is more + // robust than host-based and IP-based dialing (because several server + // can have similar hostnames). + name = "i-0123456789abcdef" + } + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + + cloud_metadata = { + aws = { + account_id = "123" + instance_id = "123" + region = "us-east-1" + vpc_id = "123" + integration = "foo" + subnet_id = "123" + } } + } } diff --git a/terraform/gen/main.go b/terraform/gen/main.go index 0539687f8..6aefb3383 100644 --- a/terraform/gen/main.go +++ b/terraform/gen/main.go @@ -112,8 +112,8 @@ type payload struct { // deprecated namespace parameter (always the default namespace). Namespaced bool // ForceSetKind indicates that the resource kind must be forcefully set by the provider. - // This is required for weird resources (ServerV2) that support multiple kinds. - // For those resource, we must set the kind, and don't want to have the user do it. + // This is required for some special resources (ServerV2) that support multiple kinds. + // For those resources, we must set the kind, and don't want to have the user do it. ForceSetKind string } diff --git a/terraform/gen/plural_resource.go.tpl b/terraform/gen/plural_resource.go.tpl index ade1c7760..c0c352192 100644 --- a/terraform/gen/plural_resource.go.tpl +++ b/terraform/gen/plural_resource.go.tpl @@ -124,11 +124,11 @@ func (r resourceTeleport{{.Name}}) Create(ctx context.Context, req tfsdk.CreateR {{.VarName}}Resource.Kind = {{.ForceSetKind}} {{- end}} -{{if .HasCheckAndSetDefaults -}} +{{- if .HasCheckAndSetDefaults }} err = {{.VarName}}Resource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting {{.Name}} defaults", trace.Wrap(err), "{{.Kind}}")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting {{.Name}} defaults", trace.Wrap(err), "{{.Kind}}")) + return } {{- end}} diff --git a/terraform/protoc-gen-terraform-teleport.yaml b/terraform/protoc-gen-terraform-teleport.yaml index 9dc836876..b4f148070 100644 --- a/terraform/protoc-gen-terraform-teleport.yaml +++ b/terraform/protoc-gen-terraform-teleport.yaml @@ -458,7 +458,7 @@ validators: ServerV2.Version: - UseVersionBetween(2,2) ServerV2.SubKind: - - UseValueIn([]string{"openssh", "openssh-ec2-ice"}) + - UseValueIn("openssh", "openssh-ec2-ice") SessionRecordingConfigV2.Version: - UseVersionBetween(2,2) SessionRecordingConfigV2.Metadata.Labels: diff --git a/terraform/provider/resource_teleport_access_list.go b/terraform/provider/resource_teleport_access_list.go index bdff88c57..30d4cdde3 100755 --- a/terraform/provider/resource_teleport_access_list.go +++ b/terraform/provider/resource_teleport_access_list.go @@ -81,11 +81,10 @@ func (r resourceTeleportAccessList) Create(ctx context.Context, req tfsdk.Create resp.Diagnostics.Append(diagFromWrappedErr("Error reading AccessList", trace.Errorf("Can not convert %T to AccessList: %s", accessListResource, err), "access_list")) return } - -err = accessListResource.CheckAndSetDefaults() + err = accessListResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting AccessList defaults", trace.Wrap(err), "access_list")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting AccessList defaults", trace.Wrap(err), "access_list")) + return } id := accessListResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_app.go b/terraform/provider/resource_teleport_app.go index f7e85b169..69e1a47cf 100755 --- a/terraform/provider/resource_teleport_app.go +++ b/terraform/provider/resource_teleport_app.go @@ -78,11 +78,10 @@ func (r resourceTeleportApp) Create(ctx context.Context, req tfsdk.CreateResourc appResource := app - -err = appResource.CheckAndSetDefaults() + err = appResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting App defaults", trace.Wrap(err), "app")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting App defaults", trace.Wrap(err), "app")) + return } id := appResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_database.go b/terraform/provider/resource_teleport_database.go index d1ff590d2..2c7f4d07f 100755 --- a/terraform/provider/resource_teleport_database.go +++ b/terraform/provider/resource_teleport_database.go @@ -78,11 +78,10 @@ func (r resourceTeleportDatabase) Create(ctx context.Context, req tfsdk.CreateRe databaseResource := database - -err = databaseResource.CheckAndSetDefaults() + err = databaseResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting Database defaults", trace.Wrap(err), "db")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting Database defaults", trace.Wrap(err), "db")) + return } id := databaseResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_device_trust.go b/terraform/provider/resource_teleport_device_trust.go index e1f6462db..a7511b0d7 100755 --- a/terraform/provider/resource_teleport_device_trust.go +++ b/terraform/provider/resource_teleport_device_trust.go @@ -83,8 +83,6 @@ func (r resourceTeleportDeviceV1) Create(ctx context.Context, req tfsdk.CreateRe trustedDeviceResource := trustedDevice - - id := trustedDeviceResource.Metadata.Name _, err = r.p.Client.GetDeviceResource(ctx, id) diff --git a/terraform/provider/resource_teleport_github_connector.go b/terraform/provider/resource_teleport_github_connector.go index 715ff8a3c..5256150b7 100755 --- a/terraform/provider/resource_teleport_github_connector.go +++ b/terraform/provider/resource_teleport_github_connector.go @@ -78,11 +78,10 @@ func (r resourceTeleportGithubConnector) Create(ctx context.Context, req tfsdk.C githubConnectorResource := githubConnector - -err = githubConnectorResource.CheckAndSetDefaults() + err = githubConnectorResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting GithubConnector defaults", trace.Wrap(err), "github")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting GithubConnector defaults", trace.Wrap(err), "github")) + return } id := githubConnectorResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_login_rule.go b/terraform/provider/resource_teleport_login_rule.go index 58b32585c..fb54465c2 100755 --- a/terraform/provider/resource_teleport_login_rule.go +++ b/terraform/provider/resource_teleport_login_rule.go @@ -79,8 +79,6 @@ func (r resourceTeleportLoginRule) Create(ctx context.Context, req tfsdk.CreateR loginRuleResource := loginRule - - id := loginRuleResource.Metadata.Name _, err = r.p.Client.GetLoginRule(ctx, id) diff --git a/terraform/provider/resource_teleport_oidc_connector.go b/terraform/provider/resource_teleport_oidc_connector.go index f989d5d0e..4e6113f66 100755 --- a/terraform/provider/resource_teleport_oidc_connector.go +++ b/terraform/provider/resource_teleport_oidc_connector.go @@ -78,11 +78,10 @@ func (r resourceTeleportOIDCConnector) Create(ctx context.Context, req tfsdk.Cre oidcConnectorResource := oidcConnector - -err = oidcConnectorResource.CheckAndSetDefaults() + err = oidcConnectorResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting OIDCConnector defaults", trace.Wrap(err), "oidc")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting OIDCConnector defaults", trace.Wrap(err), "oidc")) + return } id := oidcConnectorResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_okta_import_rule.go b/terraform/provider/resource_teleport_okta_import_rule.go index 09e383b35..873f904ff 100755 --- a/terraform/provider/resource_teleport_okta_import_rule.go +++ b/terraform/provider/resource_teleport_okta_import_rule.go @@ -78,11 +78,10 @@ func (r resourceTeleportOktaImportRule) Create(ctx context.Context, req tfsdk.Cr oktaImportRuleResource := oktaImportRule - -err = oktaImportRuleResource.CheckAndSetDefaults() + err = oktaImportRuleResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting OktaImportRule defaults", trace.Wrap(err), "okta_import_rule")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting OktaImportRule defaults", trace.Wrap(err), "okta_import_rule")) + return } id := oktaImportRuleResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_provision_token.go b/terraform/provider/resource_teleport_provision_token.go index d0052350f..6bff1078b 100755 --- a/terraform/provider/resource_teleport_provision_token.go +++ b/terraform/provider/resource_teleport_provision_token.go @@ -90,11 +90,10 @@ func (r resourceTeleportProvisionToken) Create(ctx context.Context, req tfsdk.Cr provisionTokenResource := provisionToken - -err = provisionTokenResource.CheckAndSetDefaults() + err = provisionTokenResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting ProvisionToken defaults", trace.Wrap(err), "token")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting ProvisionToken defaults", trace.Wrap(err), "token")) + return } id := provisionTokenResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_role.go b/terraform/provider/resource_teleport_role.go index ecf52dd45..73979b21a 100755 --- a/terraform/provider/resource_teleport_role.go +++ b/terraform/provider/resource_teleport_role.go @@ -78,11 +78,10 @@ func (r resourceTeleportRole) Create(ctx context.Context, req tfsdk.CreateResour roleResource := role - -err = roleResource.CheckAndSetDefaults() + err = roleResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting Role defaults", trace.Wrap(err), "role")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting Role defaults", trace.Wrap(err), "role")) + return } id := roleResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_saml_connector.go b/terraform/provider/resource_teleport_saml_connector.go index 03a933749..8d1e3aa72 100755 --- a/terraform/provider/resource_teleport_saml_connector.go +++ b/terraform/provider/resource_teleport_saml_connector.go @@ -78,11 +78,10 @@ func (r resourceTeleportSAMLConnector) Create(ctx context.Context, req tfsdk.Cre samlConnectorResource := samlConnector - -err = samlConnectorResource.CheckAndSetDefaults() + err = samlConnectorResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting SAMLConnector defaults", trace.Wrap(err), "saml")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting SAMLConnector defaults", trace.Wrap(err), "saml")) + return } id := samlConnectorResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_server.go b/terraform/provider/resource_teleport_server.go index ef8d9d371..fcfdde6da 100755 --- a/terraform/provider/resource_teleport_server.go +++ b/terraform/provider/resource_teleport_server.go @@ -80,11 +80,10 @@ func (r resourceTeleportServer) Create(ctx context.Context, req tfsdk.CreateReso serverResource := server serverResource.Kind = apitypes.KindNode - -err = serverResource.CheckAndSetDefaults() + err = serverResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting Server defaults", trace.Wrap(err), "node")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting Server defaults", trace.Wrap(err), "node")) + return } id := serverResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_trusted_cluster.go b/terraform/provider/resource_teleport_trusted_cluster.go index 6d93a2907..c1fc090d8 100755 --- a/terraform/provider/resource_teleport_trusted_cluster.go +++ b/terraform/provider/resource_teleport_trusted_cluster.go @@ -78,11 +78,10 @@ func (r resourceTeleportTrustedCluster) Create(ctx context.Context, req tfsdk.Cr trustedClusterResource := trustedCluster - -err = trustedClusterResource.CheckAndSetDefaults() + err = trustedClusterResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting TrustedCluster defaults", trace.Wrap(err), "trusted_cluster")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting TrustedCluster defaults", trace.Wrap(err), "trusted_cluster")) + return } id := trustedClusterResource.Metadata.Name diff --git a/terraform/provider/resource_teleport_user.go b/terraform/provider/resource_teleport_user.go index d5448d820..190818c0f 100755 --- a/terraform/provider/resource_teleport_user.go +++ b/terraform/provider/resource_teleport_user.go @@ -78,11 +78,10 @@ func (r resourceTeleportUser) Create(ctx context.Context, req tfsdk.CreateResour userResource := user - -err = userResource.CheckAndSetDefaults() + err = userResource.CheckAndSetDefaults() if err != nil { - resp.Diagnostics.Append(diagFromWrappedErr("Error setting User defaults", trace.Wrap(err), "user")) - return + resp.Diagnostics.Append(diagFromWrappedErr("Error setting User defaults", trace.Wrap(err), "user")) + return } id := userResource.Metadata.Name diff --git a/terraform/test/server_test.go b/terraform/test/server_test.go index 322ffc16e..655957f8a 100644 --- a/terraform/test/server_test.go +++ b/terraform/test/server_test.go @@ -17,13 +17,14 @@ limitations under the License. package test import ( + "time" + "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" "github.com/gravitational/trace" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/require" - "time" ) func (s *TerraformSuite) TestOpenSSHServer() { diff --git a/terraform/tfschema/types_terraform.go b/terraform/tfschema/types_terraform.go index 76c823d1f..52ce16e49 100644 --- a/terraform/tfschema/types_terraform.go +++ b/terraform/tfschema/types_terraform.go @@ -723,7 +723,7 @@ func GenSchemaServerV2(ctx context.Context) (github_com_hashicorp_terraform_plug Description: "SubKind is an optional resource sub kind, used in some resources", Required: true, Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, - Validators: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributeValidator{UseValueIn([]string{"openssh", "openssh-ec2-ice"})}, + Validators: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributeValidator{UseValueIn("openssh", "openssh-ec2-ice")}, }, "version": { Description: "Version is version", diff --git a/terraform/tfschema/validators.go b/terraform/tfschema/validators.go index c08a1382c..e28af9d28 100644 --- a/terraform/tfschema/validators.go +++ b/terraform/tfschema/validators.go @@ -231,8 +231,8 @@ func (v AnyOfValidator) Validate(ctx context.Context, req tfsdk.ValidateAttribut } // UseValueIn creates a StringValueValidator -func UseValueIn(subkinds []string) tfsdk.AttributeValidator { - return StringValueValidator{subkinds} +func UseValueIn(allowed ...string) tfsdk.AttributeValidator { + return StringValueValidator{allowed} } // StringValueValidator validates that a resource string field is in a set of allowed values. @@ -272,5 +272,4 @@ func (v StringValueValidator) Validate(_ context.Context, req tfsdk.ValidateAttr if !slices.Contains(v.AllowedValues, value.Value) { resp.Diagnostics.AddError("Field validation error", fmt.Sprintf("Attribute %v (%v) is not in the allowed set (%v).", req.AttributePath.String(), value.Value, v.AllowedValues)) } - return } From e3a5913c13c7c96da036b78a06bd64cedf8f8291 Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Wed, 13 Mar 2024 12:58:21 -0400 Subject: [PATCH 10/11] re-render reference --- terraform/reference.mdx | 52 +++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/terraform/reference.mdx b/terraform/reference.mdx index 4db0d8f78..db2f6219c 100755 --- a/terraform/reference.mdx +++ b/terraform/reference.mdx @@ -2278,19 +2278,47 @@ Example: ``` resource "teleport_server" "ssh_agentless" { - version = "v2" - sub_kind = "openssh" - // Name is not required for servers, this is a special case. - // When a name is not set, an UUID will be generated by Teleport and - // imported back into Terraform. - // Giving unique IDs to servers allows UUID-based dialing (as opposed to - // host-based dialing and IP-based dialing) which is more robust than its - // counterparts as it can point to a specific server if multiple servers - // share the same hostname/ip. - spec = { - addr = "127.0.0.1:22" - hostname = "test.local" + version = "v2" + sub_kind = "openssh" + // Name is not required for servers, this is a special case. + // When a name is not set, an UUID will be generated by Teleport and + // imported back into Terraform. + // Giving unique IDs to servers allows UUID-based dialing (as opposed to + // host-based dialing and IP-based dialing) which is more robust than its + // counterparts as it can point to a specific server if multiple servers + // share the same hostname/ip. + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + } +} + +resource "teleport_server" "ssh_agentless_eice" { + version = "v2" + sub_kind = "openssh-ec2-ice" + metadata = { + // It is recommended to put the instance ID as a name for EC2 Instance Connect + // When dialing to this instance, teleport will detect that this is an + // AWS instance ID an will contact this specific instance. This is more + // robust than host-based and IP-based dialing (because several server + // can have similar hostnames). + name = "i-0123456789abcdef" + } + spec = { + addr = "127.0.0.1:22" + hostname = "test.local" + + cloud_metadata = { + aws = { + account_id = "123" + instance_id = "123" + region = "us-east-1" + vpc_id = "123" + integration = "foo" + subnet_id = "123" + } } + } } ``` From c005d52c9503e5b8d82c011ee136e06e3d9ea35b Mon Sep 17 00:00:00 2001 From: Hugo Shaka Date: Wed, 20 Mar 2024 14:03:31 -0400 Subject: [PATCH 11/11] Update terraform/example/server.tf.example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marco André Dinis --- terraform/example/server.tf.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/example/server.tf.example b/terraform/example/server.tf.example index 103a076e0..a6a08b393 100644 --- a/terraform/example/server.tf.example +++ b/terraform/example/server.tf.example @@ -18,12 +18,12 @@ resource "teleport_server" "ssh_agentless_eice" { version = "v2" sub_kind = "openssh-ec2-ice" metadata = { - // It is recommended to put the instance ID as a name for EC2 Instance Connect + // It is recommended to put the account and instance ID as a name for EC2 Instance Connect // When dialing to this instance, teleport will detect that this is an // AWS instance ID an will contact this specific instance. This is more // robust than host-based and IP-based dialing (because several server // can have similar hostnames). - name = "i-0123456789abcdef" + name = "123456789012-i-0123456789abcdef" } spec = { addr = "127.0.0.1:22"