diff --git a/terraform/example/server.tf.example b/terraform/example/server.tf.example new file mode 100644 index 000000000..a6a08b393 --- /dev/null +++ b/terraform/example/server.tf.example @@ -0,0 +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" + } +} + +resource "teleport_server" "ssh_agentless_eice" { + version = "v2" + sub_kind = "openssh-ec2-ice" + metadata = { + // 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 = "123456789012-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 16fec926c..6aefb3383 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 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 } 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..c0c352192 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 . }} ) @@ -116,9 +119,22 @@ 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, 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,14 +148,6 @@ func (r resourceTeleport{{.Name}}) Create(ctx context.Context, req tfsdk.CreateR return } - {{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}}")) @@ -159,7 +167,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 +239,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 +308,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 +339,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 +394,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 +405,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/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 1bd336309..b4f148070 100644 --- a/terraform/protoc-gen-terraform-teleport.yaml +++ b/terraform/protoc-gen-terraform-teleport.yaml @@ -17,6 +17,7 @@ types: - "ProvisionTokenV2" - "RoleV6" - "SAMLConnectorV2" + - "ServerV2" - "SessionRecordingConfigV2" - "TrustedClusterV2" - "UserV2" @@ -101,6 +102,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()" SessionRecordingConfigV2: - name: id @@ -144,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 @@ -241,7 +252,23 @@ computed_fields: - "SAMLConnectorV2.Spec.EncryptionKeyPair.Cert" - "SAMLConnectorV2.Kind" - # Session recording + # 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 - "SessionRecordingConfigV2.Spec.Mode" - "SessionRecordingConfigV2.Kind" @@ -290,7 +317,7 @@ required_fields: - "ProvisionTokenV2.Spec.Roles" - "ProvisionTokenV2.Version" - # Role + # Role - "RoleV6.Metadata.Name" - "RoleV6.Version" @@ -301,6 +328,10 @@ required_fields: - "SAMLConnectorV2.Metadata.Name" - "SAMLConnectorV2.Version" + # Server + - "ServerV2.Version" + - "ServerV2.SubKind" + # Trusted cluster - "TrustedClusterV2.Metadata.Name" - "TrustedClusterV2.Version" @@ -340,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 @@ -376,6 +410,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()" @@ -418,6 +455,10 @@ validators: - UseVersionBetween(2,2) SAMLConnectorV2.Spec: - UseAnyOfValidator("entity_descriptor", "entity_descriptor_url") + ServerV2.Version: + - UseVersionBetween(2,2) + ServerV2.SubKind: + - UseValueIn("openssh", "openssh-ec2-ice") SessionRecordingConfigV2.Version: - UseVersionBetween(2,2) SessionRecordingConfigV2.Metadata.Labels: 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_access_list.go b/terraform/provider/resource_teleport_access_list.go index 256fbe9a5..30d4cdde3 100755 --- a/terraform/provider/resource_teleport_access_list.go +++ b/terraform/provider/resource_teleport_access_list.go @@ -81,6 +81,12 @@ 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 +103,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..69e1a47cf 100755 --- a/terraform/provider/resource_teleport_app.go +++ b/terraform/provider/resource_teleport_app.go @@ -78,6 +78,12 @@ 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 +100,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..2c7f4d07f 100755 --- a/terraform/provider/resource_teleport_database.go +++ b/terraform/provider/resource_teleport_database.go @@ -78,6 +78,12 @@ 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 +100,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..a7511b0d7 100755 --- a/terraform/provider/resource_teleport_device_trust.go +++ b/terraform/provider/resource_teleport_device_trust.go @@ -82,6 +82,7 @@ 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 +99,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..5256150b7 100755 --- a/terraform/provider/resource_teleport_github_connector.go +++ b/terraform/provider/resource_teleport_github_connector.go @@ -78,6 +78,12 @@ 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 +100,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..fb54465c2 100755 --- a/terraform/provider/resource_teleport_login_rule.go +++ b/terraform/provider/resource_teleport_login_rule.go @@ -78,6 +78,7 @@ 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 +95,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..4e6113f66 100755 --- a/terraform/provider/resource_teleport_oidc_connector.go +++ b/terraform/provider/resource_teleport_oidc_connector.go @@ -78,6 +78,12 @@ 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 +100,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..873f904ff 100755 --- a/terraform/provider/resource_teleport_okta_import_rule.go +++ b/terraform/provider/resource_teleport_okta_import_rule.go @@ -78,6 +78,12 @@ 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 +100,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..6bff1078b 100755 --- a/terraform/provider/resource_teleport_provision_token.go +++ b/terraform/provider/resource_teleport_provision_token.go @@ -90,6 +90,12 @@ 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 +112,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..73979b21a 100755 --- a/terraform/provider/resource_teleport_role.go +++ b/terraform/provider/resource_teleport_role.go @@ -78,6 +78,12 @@ 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 +100,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..8d1e3aa72 100755 --- a/terraform/provider/resource_teleport_saml_connector.go +++ b/terraform/provider/resource_teleport_saml_connector.go @@ -78,6 +78,12 @@ 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 +100,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 new file mode 100755 index 000000000..fcfdde6da --- /dev/null +++ b/terraform/provider/resource_teleport_server.go @@ -0,0 +1,338 @@ +// 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/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/api/defaults" + + "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 + + 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) + 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 + } + + _, 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/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..c1fc090d8 100755 --- a/terraform/provider/resource_teleport_trusted_cluster.go +++ b/terraform/provider/resource_teleport_trusted_cluster.go @@ -78,6 +78,12 @@ 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 +100,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..190818c0f 100755 --- a/terraform/provider/resource_teleport_user.go +++ b/terraform/provider/resource_teleport_user.go @@ -78,6 +78,12 @@ 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 +100,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 71c6afb6c..a2cac9775 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) @@ -2250,6 +2251,139 @@ 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" + // 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" + } + } + } +} + +``` + ## 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..459c6bdc3 --- /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..a92143d5b --- /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/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/fixtures/server_openssheice_0_create.tf b/terraform/test/fixtures/server_openssheice_0_create.tf new file mode 100644 index 000000000..37828070b --- /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..100d25364 --- /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/main_test.go b/terraform/test/main_test.go index f797cdac3..c714ea628 100644 --- a/terraform/test/main_test.go +++ b/terraform/test/main_test.go @@ -112,8 +112,9 @@ 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), 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..655957f8a --- /dev/null +++ b/terraform/test/server_test.go @@ -0,0 +1,306 @@ +/* +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. +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 ( + "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" +) + +func (s *TerraformSuite) TestOpenSSHServer() { + 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", 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_0_create.tf"), + PlanOnly: true, + }, + { + Config: s.getFixture("server_openssh_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_1_update.tf"), + PlanOnly: true, + }, + }, + }) +} + +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" + 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 + }, + }, + }, + }) +} diff --git a/terraform/tfschema/types_terraform.go b/terraform/tfschema/types_terraform.go index a0a7bbd46..a2c29919a 100644 --- a/terraform/tfschema/types_terraform.go +++ b/terraform/tfschema/types_terraform.go @@ -516,6 +516,225 @@ 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": { + Computed: true, + Description: "Name is an object name", + 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": { + 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, + }, + }), + 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{ + "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("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{ @@ -7491,6 +7710,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..e28af9d28 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,47 @@ 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(allowed ...string) tfsdk.AttributeValidator { + return StringValueValidator{allowed} +} + +// 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("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.", 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)) + } +}