From 1493bb13f6d2b421ad61ba3fb163153e6b5121ca Mon Sep 17 00:00:00 2001 From: williamong-tel Date: Mon, 27 Apr 2026 11:13:23 -0700 Subject: [PATCH] add scoped role assignments to terraform (#65632) fix template regenerate docs Update provider.go address linter issue lint delete uuid requirement - fix tests Update resource.tf fix tests fix test adjust to use envelope types fix Update presets.go Update assignment_terraform.go make docs --- .../data-sources/data-sources.mdx | 1 + .../data-sources/scoped_role_assignment.mdx | 63 + .../resources/resources.mdx | 1 + .../resources/scoped_role_assignment.mdx | 105 ++ integrations/terraform/Makefile | 8 + .../resource.tf | 40 + integrations/terraform/gen/main.go | 33 + ...oc-gen-terraform-scopedroleassignment.yaml | 71 ++ ..._source_teleport_scoped_role_assignment.go | 109 ++ integrations/terraform/provider/provider.go | 2 + ...esource_teleport_scoped_role_assignment.go | 411 +++++++ .../scoped_role_assignment_0_create.tf | 15 + .../scoped_role_assignment_1_update.tf | 15 + .../testlib/scoped_role_assignment_test.go | 147 +++ .../assignment/v1/assignment_terraform.go | 1068 +++++++++++++++++ .../access/assignment/v1/custom_types.go | 25 + lib/services/presets.go | 1 + lib/services/presets_test.go | 1 + 18 files changed, 2116 insertions(+) create mode 100644 docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_role_assignment.mdx create mode 100644 docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_role_assignment.mdx create mode 100644 integrations/terraform/examples/resources/teleport_scoped_role_assignment/resource.tf create mode 100644 integrations/terraform/protoc-gen-terraform-scopedroleassignment.yaml create mode 100755 integrations/terraform/provider/data_source_teleport_scoped_role_assignment.go create mode 100755 integrations/terraform/provider/resource_teleport_scoped_role_assignment.go create mode 100644 integrations/terraform/testlib/fixtures/scoped_role_assignment_0_create.tf create mode 100644 integrations/terraform/testlib/fixtures/scoped_role_assignment_1_update.tf create mode 100644 integrations/terraform/testlib/scoped_role_assignment_test.go create mode 100644 integrations/terraform/tfschema/scopes/access/assignment/v1/assignment_terraform.go create mode 100644 integrations/terraform/tfschema/scopes/access/assignment/v1/custom_types.go diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/data-sources.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/data-sources.mdx index dd7b82f2e47..8826d2eb0df 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/data-sources.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/data-sources.mdx @@ -40,6 +40,7 @@ The Teleport Terraform provider supports the following data-sources: - [`teleport_saml_connector`](./saml_connector.mdx) - [`teleport_saml_idp_service_provider`](./saml_idp_service_provider.mdx) - [`teleport_scoped_role`](./scoped_role.mdx) + - [`teleport_scoped_role_assignment`](./scoped_role_assignment.mdx) - [`teleport_scoped_token`](./scoped_token.mdx) - [`teleport_session_recording_config`](./session_recording_config.mdx) - [`teleport_static_host_user`](./static_host_user.mdx) diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_role_assignment.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_role_assignment.mdx new file mode 100644 index 00000000000..e87aeace9a9 --- /dev/null +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/data-sources/scoped_role_assignment.mdx @@ -0,0 +1,63 @@ +--- +title: Reference for the teleport_scoped_role_assignment Terraform data-source +sidebar_label: scoped_role_assignment +description: This page describes the supported values of the teleport_scoped_role_assignment data-source of the Teleport Terraform provider. +--- + +{/*Auto-generated file. Do not edit.*/} +{/*To regenerate, navigate to integrations/terraform and run `make docs`.*/} + +{/* Disable the outdated name check since data source fields occasionally need +to refer to these. */} +{/* vale 3rd-party-products.former-names = NO */} + +This page describes the supported values of the `teleport_scoped_role_assignment` data source of the +Teleport Terraform provider. + + + + + +{/* schema generated by tfplugindocs */} +## Schema + +### Required + +- `metadata` (Attributes) Metadata contains the resource metadata. (see [below for nested schema](#nested-schema-for-metadata)) +- `scope` (String) Scope is the scope of the role assignment resource. +- `spec` (Attributes) Spec is the role assignment specification. (see [below for nested schema](#nested-schema-for-spec)) +- `sub_kind` (String) SubKind is the resource sub-kind. +- `version` (String) Version is the resource version. + +### Nested Schema for `metadata` + +Required: + +- `name` (String) name is an object name. + +Optional: + +- `description` (String) description is object description. +- `expires` (String) expires is a global expiry time header can be set on any resource in the system. +- `labels` (Map of String) labels is a set of labels. + + +### Nested Schema for `spec` + +Required: + +- `assignments` (Attributes List) Assignments is a list of individual role @ scope assignments. (see [below for nested schema](#nested-schema-for-specassignments)) + +Optional: + +- `bot_name` (String) Name of the Bot to whom all contained assignments apply. Mutually exclusive with `user`. +- `bot_scope` (String) Scope of the Bot to whom all contained assignments apply. Required if `bot_name` is set. If specified, assignment scopes must be equal or descendent of this scope. +- `user` (String) User is the user to whom all contained assignments apply. Mutually exclusive with `bot_name`. + +### Nested Schema for `spec.assignments` + +Optional: + +- `role` (String) Roles is the name of the role that is assigned by this assignment. +- `scope` (String) Scope is the scope to which the role is assigned. This must be a member/child of the scope of the [ScopedRoleAssignment] in which this assignment is contained. + diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/resources.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/resources.mdx index 675188a780d..669cadc685d 100644 --- a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/resources.mdx +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/resources.mdx @@ -45,6 +45,7 @@ The Teleport Terraform provider supports the following resources: - [`teleport_saml_connector`](./saml_connector.mdx) - [`teleport_saml_idp_service_provider`](./saml_idp_service_provider.mdx) - [`teleport_scoped_role`](./scoped_role.mdx) + - [`teleport_scoped_role_assignment`](./scoped_role_assignment.mdx) - [`teleport_scoped_token`](./scoped_token.mdx) - [`teleport_server`](./server.mdx) - [`teleport_session_recording_config`](./session_recording_config.mdx) diff --git a/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_role_assignment.mdx b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_role_assignment.mdx new file mode 100644 index 00000000000..dd1be243a44 --- /dev/null +++ b/docs/pages/reference/infrastructure-as-code/terraform-provider/resources/scoped_role_assignment.mdx @@ -0,0 +1,105 @@ +--- +title: Reference for the teleport_scoped_role_assignment Terraform resource +sidebar_label: scoped_role_assignment +description: This page describes the supported values of the teleport_scoped_role_assignment resource of the Teleport Terraform provider. +--- + +{/*Auto-generated file. Do not edit.*/} +{/*To regenerate, navigate to integrations/terraform and run `make docs`.*/} + +{/* Disable the outdated name check since resource fields occasionally need +to refer to these. */} +{/* vale 3rd-party-products.former-names = NO */} + +This page describes the supported values of the teleport_scoped_role_assignment resource of the Teleport Terraform provider. + + + + +## Example Usage + +```hcl +# Teleport Scoped Role Assignment resource +# +# Assigns an existing scoped role to a user at a specific scope. +# The referenced scoped role must already exist. + +# resource "teleport_scoped_role" "example" { +# version = "v1" +# metadata = { +# name = "example-scoped-role" +# } + +# scope = "/staging" + +# spec = { +# assignable_scopes = ["/staging/aa"] +# rules = [{ +# resources = ["scoped_token"] +# verbs = ["read", "list"] +# }] +# } +# } + +resource "teleport_scoped_role_assignment" "example" { + version = "v1" + # sub_kind must be dynamic when creating scoped role assignments. + sub_kind = "dynamic" + metadata = { + name = "test-scoped-role-assignment" + } + + scope = "/staging" + + spec = { + user = "will" + assignments = [{ + role = "example-scoped-role" + scope = "/staging/aa" + }] + } +} +``` + +{/* schema generated by tfplugindocs */} +## Schema + +### Required + +- `metadata` (Attributes) Metadata contains the resource metadata. (see [below for nested schema](#nested-schema-for-metadata)) +- `scope` (String) Scope is the scope of the role assignment resource. +- `spec` (Attributes) Spec is the role assignment specification. (see [below for nested schema](#nested-schema-for-spec)) +- `sub_kind` (String) SubKind is the resource sub-kind. +- `version` (String) Version is the resource version. + +### Nested Schema for `metadata` + +Required: + +- `name` (String) name is an object name. + +Optional: + +- `description` (String) description is object description. +- `expires` (String) expires is a global expiry time header can be set on any resource in the system. +- `labels` (Map of String) labels is a set of labels. + + +### Nested Schema for `spec` + +Required: + +- `assignments` (Attributes List) Assignments is a list of individual role @ scope assignments. (see [below for nested schema](#nested-schema-for-specassignments)) + +Optional: + +- `bot_name` (String) Name of the Bot to whom all contained assignments apply. Mutually exclusive with `user`. +- `bot_scope` (String) Scope of the Bot to whom all contained assignments apply. Required if `bot_name` is set. If specified, assignment scopes must be equal or descendent of this scope. +- `user` (String) User is the user to whom all contained assignments apply. Mutually exclusive with `bot_name`. + +### Nested Schema for `spec.assignments` + +Optional: + +- `role` (String) Roles is the name of the role that is assigned by this assignment. +- `scope` (String) Scope is the scope to which the role is assigned. This must be a member/child of the scope of the [ScopedRoleAssignment] in which this assignment is contained. diff --git a/integrations/terraform/Makefile b/integrations/terraform/Makefile index 2bca082ddf7..9867c6c7146 100644 --- a/integrations/terraform/Makefile +++ b/integrations/terraform/Makefile @@ -192,6 +192,13 @@ endif --terraform_out=config=protoc-gen-terraform-scopedrole.yaml:./tfschema \ teleport/scopes/access/v1/role.proto + @protoc \ + -I=../../api/proto \ + -I=$(PROTOBUF_MOD_PATH) \ + --plugin=$(PROTOC_GEN_TERRAFORM) \ + --terraform_out=config=protoc-gen-terraform-scopedroleassignment.yaml:./tfschema \ + teleport/scopes/access/v1/assignment.proto + mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1/loginrule_terraform.go ./tfschema/loginrule/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1/accesslist_terraform.go ./tfschema/accesslist/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1/access_monitoring_rules_terraform.go ./tfschema/accessmonitoringrules/v1/ @@ -206,6 +213,7 @@ endif mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1/token_terraform.go ./tfschema/scopes/joining/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadcluster/v1/workloadcluster_terraform.go ./tfschema/workloadcluster/v1/ mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1/role_terraform.go ./tfschema/scopes/access/v1/ + mv ./tfschema/github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1/assignment_terraform.go ./tfschema/scopes/access/assignment/v1/ mv ./tfschema/github.com/gravitational/teleport/api/types/device_terraform.go ./tfschema/devicetrust/v1/ rm -r ./tfschema/github.com/ @go run ./gen/main.go diff --git a/integrations/terraform/examples/resources/teleport_scoped_role_assignment/resource.tf b/integrations/terraform/examples/resources/teleport_scoped_role_assignment/resource.tf new file mode 100644 index 00000000000..4f849f427ca --- /dev/null +++ b/integrations/terraform/examples/resources/teleport_scoped_role_assignment/resource.tf @@ -0,0 +1,40 @@ +# Teleport Scoped Role Assignment resource +# +# Assigns an existing scoped role to a user at a specific scope. +# The referenced scoped role must already exist. + +# resource "teleport_scoped_role" "example" { +# version = "v1" +# metadata = { +# name = "example-scoped-role" +# } + +# scope = "/staging" + +# spec = { +# assignable_scopes = ["/staging/aa"] +# rules = [{ +# resources = ["scoped_token"] +# verbs = ["read", "list"] +# }] +# } +# } + +resource "teleport_scoped_role_assignment" "example" { + version = "v1" + # sub_kind must be dynamic when creating scoped role assignments. + sub_kind = "dynamic" + metadata = { + name = "test-scoped-role-assignment" + } + + scope = "/staging" + + spec = { + user = "will" + assignments = [{ + role = "example-scoped-role" + scope = "/staging/aa" + }] + } +} diff --git a/integrations/terraform/gen/main.go b/integrations/terraform/gen/main.go index a58f0e7e906..84c7c4eb3a3 100644 --- a/integrations/terraform/gen/main.go +++ b/integrations/terraform/gen/main.go @@ -1068,6 +1068,37 @@ var ( DeleteRequest: "DeleteScopedRoleRequest", }, } + + scopedRoleAssignment = payload{ + Name: "ScopedRoleAssignment", + TypeName: "ScopedRoleAssignment", + VarName: "scopedRoleAssignment", + GetMethod: "ScopedAccessServiceClient().GetScopedRoleAssignment", + CreateMethod: "ScopedAccessServiceClient().CreateScopedRoleAssignment", + UpdateMethod: "ScopedAccessServiceClient().UpsertScopedRoleAssignment", + UpsertMethodArity: 2, + DeleteMethod: "ScopedAccessServiceClient().DeleteScopedRoleAssignment", + ID: "scopedRoleAssignment.Metadata.Name", + Kind: "scoped_role_assignment", + HasStaticID: false, + ProtoPackage: "accessv1", + ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1", + SchemaPackage: "assignmentschemav1", + SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/assignment/v1", + TerraformResourceType: "teleport_scoped_role_assignment", + IsPlainStruct: true, + ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/lib/scopes/access\""}, + ForceSetKind: "apitypes.KindScopedRoleAssignment", + RequestWrapper: &RequestWrapper{ + RequestResourceField: "Assignment", + GetRequest: "GetScopedRoleAssignmentRequest", + CreateRequest: "CreateScopedRoleAssignmentRequest", + UpdateRequest: "UpsertScopedRoleAssignmentRequest", + DeleteRequest: "DeleteScopedRoleAssignmentRequest", + }, + DefaultSubKind: "\"dynamic\"", + } + scopedToken = payload{ Name: "ScopedToken", TypeName: "ScopedToken", @@ -1245,6 +1276,8 @@ func genTFSchema() { generateDataSource(retrievalModel, singularDataSource) generateResource(scopedRole, pluralResource) generateDataSource(scopedRole, pluralDataSource) + generateResource(scopedRoleAssignment, pluralResource) + generateDataSource(scopedRoleAssignment, pluralDataSource) generateResource(scopedToken, pluralResource) generateDataSource(scopedToken, pluralDataSource) generateResource(workloadCluster, pluralResource) diff --git a/integrations/terraform/protoc-gen-terraform-scopedroleassignment.yaml b/integrations/terraform/protoc-gen-terraform-scopedroleassignment.yaml new file mode 100644 index 00000000000..8f5ee333eef --- /dev/null +++ b/integrations/terraform/protoc-gen-terraform-scopedroleassignment.yaml @@ -0,0 +1,71 @@ +--- +target_package_name: "v1" +default_package_name: "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" +use_state_for_unknown_by_default: true + +# Top-level type names to export +types: + - "ScopedRoleAssignment" + +# These import paths were not being automatically picked up by +# protoc-gen-terraform without these overrides +import_path_overrides: + "types": "github.com/gravitational/teleport/api/types" + "wrappers": "github.com/gravitational/teleport/api/types/wrappers" + "timestamppb": "google.golang.org/protobuf/types/known/timestamppb" + "v1": "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + "github_com_gravitational_teleport_integrations_terraform_tfschema": "github.com/gravitational/teleport/integrations/terraform/tfschema" + +# id field is required for integration tests. It is not used by provider. +# We have to add it manually (might be removed in the future versions). +injected_fields: + ScopedRoleAssignment: + - name: id + type: github.com/hashicorp/terraform-plugin-framework/types.StringType + computed: true + plan_modifiers: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()" + +# These fields will be excluded +exclude_fields: + - "ScopedRoleAssignment.metadata.id" + # Usage tracking is server-side only + - "ScopedRoleAssignment.status" + +# These fields will be marked as Computed: true +computed_fields: + - "ScopedRoleAssignment.metadata.expires" + - "ScopedRoleAssignment.metadata.namespace" + - "ScopedRoleAssignment.metadata.revision" + - "ScopedRoleAssignment.kind" + +# These fields will be marked as Required: true +required_fields: + - "ScopedRoleAssignment.metadata" + - "ScopedRoleAssignment.metadata.name" + - "ScopedRoleAssignment.version" + - "ScopedRoleAssignment.sub_kind" + - "ScopedRoleAssignment.scope" + - "ScopedRoleAssignment.spec" + - "ScopedRoleAssignment.spec.assignments" + +sensitive_fields: + +plan_modifiers: + # Force to recreate resource if its name changes + Metadata.name: + - "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()" + +# This must be defined for the generator to be happy, but in reality all time +# fields are overridden (because the protobuf timestamps contain locks and the +# linter gets mad if we use raw structs instead of pointers). +time_type: + type: "PlaceholderType" + +validators: + # Expires must be in the future + Metadata.expires: + - github_com_gravitational_teleport_integrations_terraform_tfschema.MustTimeBeInFuture() + +custom_types: + "ScopedRoleAssignment.metadata.expires": Timestamp diff --git a/integrations/terraform/provider/data_source_teleport_scoped_role_assignment.go b/integrations/terraform/provider/data_source_teleport_scoped_role_assignment.go new file mode 100755 index 00000000000..b8d3b298f4b --- /dev/null +++ b/integrations/terraform/provider/data_source_teleport_scoped_role_assignment.go @@ -0,0 +1,109 @@ +// Code generated by _gen/main.go DO NOT EDIT +/* +Copyright 2015-2024 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "context" + + accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + "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" + + assignmentschemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/assignment/v1" +) + +// dataSourceTeleportScopedRoleAssignmentType is the data source metadata type +type dataSourceTeleportScopedRoleAssignmentType struct{} + +// dataSourceTeleportScopedRoleAssignment is the resource +type dataSourceTeleportScopedRoleAssignment struct { + p Provider +} + +// GetSchema returns the data source schema +func (r dataSourceTeleportScopedRoleAssignmentType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return assignmentschemav1.GenSchemaScopedRoleAssignment(ctx) +} + +// NewDataSource creates the empty data source +func (r dataSourceTeleportScopedRoleAssignmentType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { + return dataSourceTeleportScopedRoleAssignment{ + p: *(p.(*Provider)), + }, nil +} + +// Read reads teleport ScopedRoleAssignment +func (r dataSourceTeleportScopedRoleAssignment) 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 + } + var subKind types.String + diags = req.Config.GetAttribute(ctx, path.Root("sub_kind"), &subKind) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + if subKind.Value == "" { + subKind.Value = "dynamic" + } + + scopedRoleAssignmentGetResp, err := r.p.Client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: id.Value, + SubKind: subKind.Value, + }) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", trace.Wrap(err), "scoped_role_assignment")) + return + } + scopedRoleAssignmentI := scopedRoleAssignmentGetResp.GetAssignment() + + var state types.Object + resp.Diagnostics.Append(req.Config.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // Todo: Remove after updating terraform-plugin to >=v1.5.0. + // terraform-plugin-testing version <1.5.0 requires data resources to + // implement the 'id' attribute. + // https://developer.hashicorp.com/terraform/plugin/framework/acctests#no-id-found-in-attributes + v, ok := state.Attrs["id"] + if !ok || v.IsNull() { + state.Attrs["id"] = id + } + + scopedRoleAssignment := scopedRoleAssignmentI + + diags = assignmentschemav1.CopyScopedRoleAssignmentToTerraform(ctx, scopedRoleAssignment, &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/integrations/terraform/provider/provider.go b/integrations/terraform/provider/provider.go index 9ab7059b372..733ec07e9a2 100644 --- a/integrations/terraform/provider/provider.go +++ b/integrations/terraform/provider/provider.go @@ -586,6 +586,7 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp "teleport_scoped_token": resourceTeleportScopedTokenType{}, "teleport_workload_cluster": resourceTeleportWorkloadClusterType{}, "teleport_scoped_role": resourceTeleportScopedRoleType{}, + "teleport_scoped_role_assignment": resourceTeleportScopedRoleAssignmentType{}, }, nil } @@ -628,6 +629,7 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc "teleport_integration": dataSourceTeleportIntegrationType{}, "teleport_scoped_token": dataSourceTeleportScopedTokenType{}, "teleport_scoped_role": dataSourceTeleportScopedRoleType{}, + "teleport_scoped_role_assignment": dataSourceTeleportScopedRoleAssignmentType{}, // TODO(bl-nero): Add teleport_inference_* data sources after data sources // are fixed. The current problems with data sources include: // - Data sources only perform a "shallow fill", which means only setting diff --git a/integrations/terraform/provider/resource_teleport_scoped_role_assignment.go b/integrations/terraform/provider/resource_teleport_scoped_role_assignment.go new file mode 100755 index 00000000000..3b9367c846e --- /dev/null +++ b/integrations/terraform/provider/resource_teleport_scoped_role_assignment.go @@ -0,0 +1,411 @@ +// Code generated by _gen/main.go DO NOT EDIT +/* +Copyright 2015-2024 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package provider + +import ( + "context" + "fmt" + "strings" + apitypes "github.com/gravitational/teleport/lib/scopes/access" + + accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + + "github.com/gravitational/teleport/api/utils/retryutils" + "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" + + assignmentschemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/assignment/v1" +) + +// resourceTeleportScopedRoleAssignmentType is the resource metadata type +type resourceTeleportScopedRoleAssignmentType struct{} + +// resourceTeleportScopedRoleAssignment is the resource +type resourceTeleportScopedRoleAssignment struct { + p Provider +} + +// GetSchema returns the resource schema +func (r resourceTeleportScopedRoleAssignmentType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + return assignmentschemav1.GenSchemaScopedRoleAssignment(ctx) +} + +// NewResource creates the empty resource +func (r resourceTeleportScopedRoleAssignmentType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { + return resourceTeleportScopedRoleAssignment{ + p: *(p.(*Provider)), + }, nil +} + +// Create creates the ScopedRoleAssignment +func (r resourceTeleportScopedRoleAssignment) 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 + } + + scopedRoleAssignment := &accessv1.ScopedRoleAssignment{} + diags = assignmentschemav1.CopyScopedRoleAssignmentFromTerraform(ctx, plan, scopedRoleAssignment) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + + scopedRoleAssignmentResource := scopedRoleAssignment + + scopedRoleAssignmentResource.Kind = apitypes.KindScopedRoleAssignment + if scopedRoleAssignmentResource.SubKind == "" { + scopedRoleAssignmentResource.SubKind = "dynamic" + } + + id := scopedRoleAssignmentResource.Metadata.Name + + _, err = r.p.Client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: id, + SubKind: scopedRoleAssignmentResource.SubKind, + }) + if !trace.IsNotFound(err) { + if err == nil { + existErr := fmt.Sprintf("ScopedRoleAssignment exists in Teleport. Either remove it (tctl rm scoped_role_assignment/%v)"+ + " or import it to the existing state (terraform import teleport_scoped_role_assignment.%v %v)", id, id, id) + + resp.Diagnostics.Append(diagFromErr("ScopedRoleAssignment exists in Teleport", trace.Errorf(existErr))) + return + } + + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", trace.Wrap(err), "scoped_role_assignment")) + return + } + + _, err = r.p.Client.ScopedAccessServiceClient().CreateScopedRoleAssignment(ctx, &accessv1.CreateScopedRoleAssignmentRequest{ + Assignment: scopedRoleAssignmentResource, + }) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error creating ScopedRoleAssignment", trace.Wrap(err), "scoped_role_assignment")) + return + } + var scopedRoleAssignmentI *accessv1.ScopedRoleAssignment + // Try getting the resource until it exists. + tries := 0 + retry, err := retryutils.NewRetryV2(retryutils.RetryV2Config{ + Driver: retryutils.NewExponentialDriver(r.p.RetryConfig.Base), + First: r.p.RetryConfig.Base, + Max: r.p.RetryConfig.Cap, + Jitter: retryutils.HalfJitter, + }) + if err != nil { + return + } + for { + tries = tries + 1 + scopedRoleAssignmentGetResp, getErr := r.p.Client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: id, + SubKind: scopedRoleAssignmentResource.SubKind, + }) + err = getErr + if err == nil { + scopedRoleAssignmentI = scopedRoleAssignmentGetResp.GetAssignment() + } + if trace.IsNotFound(err) { + select { + case <-ctx.Done(): + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", trace.Wrap(ctx.Err()), "scoped_role_assignment")) + return + case <-retry.After(): + } + if tries >= r.p.RetryConfig.MaxTries { + diagMessage := fmt.Sprintf("Error reading ScopedRoleAssignment (tried %d times) - state outdated, please import resource", tries) + resp.Diagnostics.AddError(diagMessage, "scoped_role_assignment") + return + } + continue + } + break + } + + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", trace.Wrap(err), "scoped_role_assignment")) + return + } + + scopedRoleAssignmentResource = scopedRoleAssignmentI + + scopedRoleAssignment = scopedRoleAssignmentResource + + diags = assignmentschemav1.CopyScopedRoleAssignmentToTerraform(ctx, scopedRoleAssignment, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + plan.Attrs["id"] = types.String{Value: scopedRoleAssignment.Metadata.Name} + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read reads teleport ScopedRoleAssignment +func (r resourceTeleportScopedRoleAssignment) 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 + } + var subKind types.String + diags = req.State.GetAttribute(ctx, path.Root("sub_kind"), &subKind) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + if subKind.Value == "" { + subKind.Value = "dynamic" + } + + scopedRoleAssignmentGetResp, err := r.p.Client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: id.Value, + SubKind: subKind.Value, + }) + if trace.IsNotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", trace.Wrap(err), "scoped_role_assignment")) + return + } + scopedRoleAssignmentI := scopedRoleAssignmentGetResp.GetAssignment() + scopedRoleAssignment := scopedRoleAssignmentI + diags = assignmentschemav1.CopyScopedRoleAssignmentToTerraform(ctx, scopedRoleAssignment, &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 ScopedRoleAssignment +func (r resourceTeleportScopedRoleAssignment) 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 + } + + scopedRoleAssignment := &accessv1.ScopedRoleAssignment{} + diags = assignmentschemav1.CopyScopedRoleAssignmentFromTerraform(ctx, plan, scopedRoleAssignment) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + scopedRoleAssignmentResource := scopedRoleAssignment + + scopedRoleAssignmentResource.Kind = apitypes.KindScopedRoleAssignment + + + if scopedRoleAssignmentResource.SubKind == "" { + scopedRoleAssignmentResource.SubKind = "dynamic" + } + name := scopedRoleAssignmentResource.Metadata.Name + + scopedRoleAssignmentBeforeResp, err := r.p.Client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: name, + SubKind: scopedRoleAssignmentResource.SubKind, + }) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", err, "scoped_role_assignment")) + return + } + scopedRoleAssignmentBefore := scopedRoleAssignmentBeforeResp.GetAssignment() + + _, err = r.p.Client.ScopedAccessServiceClient().UpsertScopedRoleAssignment(ctx, &accessv1.UpsertScopedRoleAssignmentRequest{ + Assignment: scopedRoleAssignmentResource, + }) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error updating ScopedRoleAssignment", err, "scoped_role_assignment")) + return + } + var scopedRoleAssignmentI *accessv1.ScopedRoleAssignment + + tries := 0 + retry, err := retryutils.NewRetryV2(retryutils.RetryV2Config{ + Driver: retryutils.NewExponentialDriver(r.p.RetryConfig.Base), + First: r.p.RetryConfig.Base, + Max: r.p.RetryConfig.Cap, + Jitter: retryutils.HalfJitter, + }) + if err != nil { + return + } + for { + tries = tries + 1 + scopedRoleAssignmentGetResp, getErr := r.p.Client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: name, + SubKind: scopedRoleAssignmentResource.SubKind, + }) + err = getErr + if err == nil { + scopedRoleAssignmentI = scopedRoleAssignmentGetResp.GetAssignment() + } + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", err, "scoped_role_assignment")) + return + } + if scopedRoleAssignmentBefore.GetMetadata().Revision != scopedRoleAssignmentI.GetMetadata().Revision || false { + break + } + + select { + case <-ctx.Done(): + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", trace.Wrap(ctx.Err()), "scoped_role_assignment")) + return + case <-retry.After(): + } + if tries >= r.p.RetryConfig.MaxTries { + diagMessage := fmt.Sprintf("Error reading ScopedRoleAssignment (tried %d times) - state outdated, please import resource", tries) + resp.Diagnostics.AddError(diagMessage, "scoped_role_assignment") + return + } + } + + scopedRoleAssignmentResource = scopedRoleAssignmentI + + diags = assignmentschemav1.CopyScopedRoleAssignmentToTerraform(ctx, scopedRoleAssignment, &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 ScopedRoleAssignment +func (r resourceTeleportScopedRoleAssignment) 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 + } + var subKind types.String + diags = req.State.GetAttribute(ctx, path.Root("sub_kind"), &subKind) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + if subKind.Value == "" { + subKind.Value = "dynamic" + } + + _, err := r.p.Client.ScopedAccessServiceClient().DeleteScopedRoleAssignment(ctx, &accessv1.DeleteScopedRoleAssignmentRequest{ + Name: id.Value, + SubKind: subKind.Value, + }) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error deleting ScopedRoleAssignment", trace.Wrap(err), "scoped_role_assignment")) + return + } + + resp.State.RemoveResource(ctx) +} + +// ImportState imports ScopedRoleAssignment state +func (r resourceTeleportScopedRoleAssignment) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) { + subKind := "dynamic" + name := req.ID + if before, after, ok := strings.Cut(req.ID, "/"); ok { + subKind = before + name = after + } + scopedRoleAssignmentGetResp, err := r.p.Client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: name, + SubKind: subKind, + }) + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", trace.Wrap(err), "scoped_role_assignment")) + return + } + scopedRoleAssignment := scopedRoleAssignmentGetResp.GetAssignment() + if err != nil { + resp.Diagnostics.Append(diagFromWrappedErr("Error reading ScopedRoleAssignment", trace.Wrap(err), "scoped_role_assignment")) + return + } + + scopedRoleAssignmentResource := scopedRoleAssignment + + + var state types.Object + + diags := resp.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + diags = assignmentschemav1.CopyScopedRoleAssignmentToTerraform(ctx, scopedRoleAssignmentResource, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + id := scopedRoleAssignment.Metadata.Name + + state.Attrs["id"] = types.String{Value: id} + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/integrations/terraform/testlib/fixtures/scoped_role_assignment_0_create.tf b/integrations/terraform/testlib/fixtures/scoped_role_assignment_0_create.tf new file mode 100644 index 00000000000..934b9810fc4 --- /dev/null +++ b/integrations/terraform/testlib/fixtures/scoped_role_assignment_0_create.tf @@ -0,0 +1,15 @@ +resource "teleport_scoped_role_assignment" "test" { + version = "v1" + metadata = { + name = "test-scoped-role-assignment" + } + scope = "/staging" + sub_kind = "dynamic" + spec = { + user = "testuser" + assignments = [{ + role = "test-scoped-role" + scope = "/staging/aa" + }] + } +} diff --git a/integrations/terraform/testlib/fixtures/scoped_role_assignment_1_update.tf b/integrations/terraform/testlib/fixtures/scoped_role_assignment_1_update.tf new file mode 100644 index 00000000000..60cdaee8c07 --- /dev/null +++ b/integrations/terraform/testlib/fixtures/scoped_role_assignment_1_update.tf @@ -0,0 +1,15 @@ +resource "teleport_scoped_role_assignment" "test" { + version = "v1" + metadata = { + name = "test-scoped-role-assignment" + } + scope = "/staging" + sub_kind = "dynamic" + spec = { + user = "testuser" + assignments = [{ + role = "test-scoped-role" + scope = "/staging/aaaa" + }] + } +} diff --git a/integrations/terraform/testlib/scoped_role_assignment_test.go b/integrations/terraform/testlib/scoped_role_assignment_test.go new file mode 100644 index 00000000000..e07f27eca69 --- /dev/null +++ b/integrations/terraform/testlib/scoped_role_assignment_test.go @@ -0,0 +1,147 @@ +// Teleport +// Copyright (C) 2026 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package testlib + +import ( + "fmt" + "time" + + "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/assert" + "github.com/stretchr/testify/require" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/scopes/access" +) + +func (s *TerraformSuiteOSS) TestScopedRoleAssignment() { + t := s.T() + ctx := t.Context() + t.Setenv("TELEPORT_UNSTABLE_SCOPES", "yes") + + checkDestroyed := func(state *terraform.State) error { + accessClient := s.client.ScopedAccessServiceClient() + + _, err := accessClient.GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: "test-scoped-role-assignment", + SubKind: access.SubKindDynamic, + }) + if !trace.IsNotFound(err) { + return trace.Errorf("expected not found, actual: %v", err) + } + return nil + } + + name := "teleport_scoped_role_assignment.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + CheckDestroy: checkDestroyed, + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: s.getFixture("scoped_role_assignment_0_create.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "kind", access.KindScopedRoleAssignment), + resource.TestCheckResourceAttr(name, "sub_kind", access.SubKindDynamic), + resource.TestCheckResourceAttr(name, "scope", "/staging"), + resource.TestCheckResourceAttr(name, "spec.user", "testuser"), + resource.TestCheckResourceAttr(name, "spec.assignments.0.role", "test-scoped-role"), + resource.TestCheckResourceAttr(name, "spec.assignments.0.scope", "/staging/aa"), + ), + }, + { + Config: s.getFixture("scoped_role_assignment_1_update.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "scope", "/staging"), + resource.TestCheckResourceAttr(name, "spec.assignments.0.role", "test-scoped-role"), + resource.TestCheckResourceAttr(name, "spec.assignments.0.scope", "/staging/aaaa"), + ), + }, + { + Config: s.getFixture("scoped_role_assignment_1_update.tf"), + PlanOnly: true, + }, + }, + }) +} + +func (s *TerraformSuiteOSS) TestImportScopedRoleAssignment() { + t := s.T() + ctx := t.Context() + t.Setenv("TELEPORT_UNSTABLE_SCOPES", "yes") + accessClient := s.client.ScopedAccessServiceClient() + + r := "teleport_scoped_role_assignment" + id := "test-import-sra" + name := r + "." + id + + assignment := &accessv1.ScopedRoleAssignment{ + Kind: access.KindScopedRoleAssignment, + SubKind: access.SubKindDynamic, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: id, + }, + Scope: "/staging", + Spec: &accessv1.ScopedRoleAssignmentSpec{ + User: "testuser", + Assignments: []*accessv1.Assignment{ + { + Role: "test-scoped-role", + Scope: "/staging/aa", + }, + }, + }, + } + + _, err := accessClient.CreateScopedRoleAssignment(ctx, &accessv1.CreateScopedRoleAssignmentRequest{ + Assignment: assignment, + }) + require.NoError(t, err) + + require.EventuallyWithT(t, func(t *assert.CollectT) { + _, err := accessClient.GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{ + Name: id, + SubKind: access.SubKindDynamic, + }) + require.NoError(t, err) + }, 5*time.Second, time.Second) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: s.terraformProviders, + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf("%s\nresource %q %q { }", s.terraformConfig, r, id), + ResourceName: name, + ImportState: true, + ImportStateId: id, + ImportStateCheck: func(state []*terraform.InstanceState) error { + require.Equal(t, access.KindScopedRoleAssignment, state[0].Attributes["kind"]) + require.Equal(t, "/staging", state[0].Attributes["scope"]) + require.Equal(t, "testuser", state[0].Attributes["spec.user"]) + return nil + }, + }, + }, + }) +} diff --git a/integrations/terraform/tfschema/scopes/access/assignment/v1/assignment_terraform.go b/integrations/terraform/tfschema/scopes/access/assignment/v1/assignment_terraform.go new file mode 100644 index 00000000000..825cc36b2bf --- /dev/null +++ b/integrations/terraform/tfschema/scopes/access/assignment/v1/assignment_terraform.go @@ -0,0 +1,1068 @@ +/* +Copyright 2015-2026 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. +*/ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: teleport/scopes/access/v1/assignment.proto + +package v1 + +import ( + context "context" + fmt "fmt" + math "math" + + proto "github.com/gogo/protobuf/proto" + _ "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + github_com_gravitational_teleport_api_gen_proto_go_teleport_header_v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1" + github_com_gravitational_teleport_integrations_terraform_tfschema "github.com/gravitational/teleport/integrations/terraform/tfschema" + github_com_hashicorp_terraform_plugin_framework_attr "github.com/hashicorp/terraform-plugin-framework/attr" + github_com_hashicorp_terraform_plugin_framework_diag "github.com/hashicorp/terraform-plugin-framework/diag" + github_com_hashicorp_terraform_plugin_framework_tfsdk "github.com/hashicorp/terraform-plugin-framework/tfsdk" + github_com_hashicorp_terraform_plugin_framework_types "github.com/hashicorp/terraform-plugin-framework/types" + github_com_hashicorp_terraform_plugin_go_tftypes "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// GenSchemaScopedRoleAssignment returns tfsdk.Schema definition for ScopedRoleAssignment +func GenSchemaScopedRoleAssignment(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 the 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": GenSchemaTimestamp(ctx, github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + Computed: true, + Description: "expires is a global expiry time header can be set on any resource in the system.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Validators: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributeValidator{github_com_gravitational_teleport_integrations_terraform_tfschema.MustTimeBeInFuture()}, + }), + "labels": { + Description: "labels is a set of labels.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.MapType{ElemType: github_com_hashicorp_terraform_plugin_framework_types.StringType}, + }, + "name": { + Description: "name is an object name.", + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()}, + Required: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "namespace": { + Computed: true, + Description: "namespace is object namespace. The field should be called \"namespace\" when it returns in Teleport 2.4.", + Optional: true, + PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.UseStateForUnknown()}, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "revision": { + Computed: true, + 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, + 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, + }, + }), + Description: "Metadata contains the resource metadata.", + Required: true, + }, + "scope": { + Description: "Scope is the scope of the role assignment resource.", + Required: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "spec": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.SingleNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "assignments": { + Attributes: github_com_hashicorp_terraform_plugin_framework_tfsdk.ListNestedAttributes(map[string]github_com_hashicorp_terraform_plugin_framework_tfsdk.Attribute{ + "role": { + Description: "Roles is the name of the role that is assigned by this assignment.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "scope": { + Description: "Scope is the scope to which the role is assigned. This must be a member/child of the scope of the [ScopedRoleAssignment] in which this assignment is contained.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Assignments is a list of individual role @ scope assignments.", + Required: true, + }, + "bot_name": { + Description: "Name of the Bot to whom all contained assignments apply. Mutually exclusive with `user`.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "bot_scope": { + Description: "Scope of the Bot to whom all contained assignments apply. Required if `bot_name` is set. If specified, assignment scopes must be equal or descendent of this scope.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "user": { + Description: "User is the user to whom all contained assignments apply. Mutually exclusive with `bot_name`.", + Optional: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }), + Description: "Spec is the role assignment specification.", + Required: true, + }, + "sub_kind": { + Description: "SubKind is the resource sub-kind.", + Required: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + "version": { + Description: "Version is the resource version.", + Required: true, + Type: github_com_hashicorp_terraform_plugin_framework_types.StringType, + }, + }}, nil +} + +// CopyScopedRoleAssignmentFromTerraform copies contents of the source Terraform object into a target struct +func CopyScopedRoleAssignmentFromTerraform(_ context.Context, tf github_com_hashicorp_terraform_plugin_framework_types.Object, obj *github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.ScopedRoleAssignment) 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{"ScopedRoleAssignment.kind"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.sub_kind"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.sub_kind", "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{"ScopedRoleAssignment.version"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.metadata", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Metadata = nil + if !v.Null && !v.Unknown { + tf := v + obj.Metadata = &github_com_gravitational_teleport_api_gen_proto_go_teleport_header_v1.Metadata{} + obj := obj.Metadata + { + a, ok := tf.Attrs["name"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.metadata.name"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.namespace"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.description"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.labels"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Map) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.expires"}) + } + CopyFromTimestamp(diags, a, &obj.Expires) + } + { + a, ok := tf.Attrs["revision"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.metadata.revision"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.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["scope"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.scope"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.scope", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Scope = t + } + } + } + { + a, ok := tf.Attrs["spec"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.spec"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.spec", "github.com/hashicorp/terraform-plugin-framework/types.Object"}) + } else { + obj.Spec = nil + if !v.Null && !v.Unknown { + tf := v + obj.Spec = &github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.ScopedRoleAssignmentSpec{} + obj := obj.Spec + { + a, ok := tf.Attrs["user"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.spec.user"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.spec.user", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.User = t + } + } + } + { + a, ok := tf.Attrs["assignments"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.spec.assignments"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.List) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.spec.assignments", "github.com/hashicorp/terraform-plugin-framework/types.List"}) + } else { + obj.Assignments = make([]*github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.Assignment, len(v.Elems)) + if !v.Null && !v.Unknown { + for k, a := range v.Elems { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.Object) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.spec.assignments", "github_com_hashicorp_terraform_plugin_framework_types.Object"}) + } else { + var t *github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.Assignment + if !v.Null && !v.Unknown { + tf := v + t = &github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.Assignment{} + obj := t + { + a, ok := tf.Attrs["role"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.spec.assignments.role"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.spec.assignments.role", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Role = t + } + } + } + { + a, ok := tf.Attrs["scope"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.spec.assignments.scope"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.spec.assignments.scope", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.Scope = t + } + } + } + } + obj.Assignments[k] = t + } + } + } + } + } + } + { + a, ok := tf.Attrs["bot_name"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.spec.bot_name"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.spec.bot_name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.BotName = t + } + } + } + { + a, ok := tf.Attrs["bot_scope"] + if !ok { + diags.Append(attrReadMissingDiag{"ScopedRoleAssignment.spec.bot_scope"}) + } else { + v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrReadConversionFailureDiag{"ScopedRoleAssignment.spec.bot_scope", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } else { + var t string + if !v.Null && !v.Unknown { + t = string(v.Value) + } + obj.BotScope = t + } + } + } + } + } + } + } + return diags +} + +// CopyScopedRoleAssignmentToTerraform copies contents of the source Terraform object into a target struct +func CopyScopedRoleAssignmentToTerraform(ctx context.Context, obj *github_com_gravitational_teleport_api_gen_proto_go_teleport_scopes_access_v1.ScopedRoleAssignment, 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{"ScopedRoleAssignment.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{"ScopedRoleAssignment.kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.sub_kind"}) + } 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{"ScopedRoleAssignment.sub_kind", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.sub_kind", "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{"ScopedRoleAssignment.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{"ScopedRoleAssignment.version", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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)) + } + } + if obj.Metadata == nil { + v.Null = true + } else { + obj := obj.Metadata + tf := &v + { + t, ok := tf.AttrTypes["name"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.name", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.namespace", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.description", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.labels"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.MapType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.labels", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.expires"}) + } else { + v := CopyToTimestamp(diags, obj.Expires, t, tf.Attrs["expires"]) + tf.Attrs["expires"] = v + } + } + { + t, ok := tf.AttrTypes["revision"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.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{"ScopedRoleAssignment.metadata.revision", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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 + } + } + } + { + t, ok := tf.AttrTypes["scope"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.scope"}) + } else { + v, ok := tf.Attrs["scope"].(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{"ScopedRoleAssignment.scope", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.scope", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Scope) == "" + } + v.Value = string(obj.Scope) + v.Unknown = false + tf.Attrs["scope"] = v + } + } + { + a, ok := tf.AttrTypes["spec"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.spec"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.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)) + } + } + if obj.Spec == nil { + v.Null = true + } else { + obj := obj.Spec + tf := &v + { + t, ok := tf.AttrTypes["user"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.spec.user"}) + } else { + v, ok := tf.Attrs["user"].(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{"ScopedRoleAssignment.spec.user", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.spec.user", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.User) == "" + } + v.Value = string(obj.User) + v.Unknown = false + tf.Attrs["user"] = v + } + } + { + a, ok := tf.AttrTypes["assignments"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.spec.assignments"}) + } else { + o, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.ListType) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.spec.assignments", "github.com/hashicorp/terraform-plugin-framework/types.ListType"}) + } else { + c, ok := tf.Attrs["assignments"].(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.Assignments)), + Null: true, + } + } else { + if c.Elems == nil { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Assignments)) + } + } + if obj.Assignments != nil { + o := o.ElemType.(github_com_hashicorp_terraform_plugin_framework_types.ObjectType) + if len(obj.Assignments) != len(c.Elems) { + c.Elems = make([]github_com_hashicorp_terraform_plugin_framework_attr.Value, len(obj.Assignments)) + } + for k, a := range obj.Assignments { + v, ok := tf.Attrs["assignments"].(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 a == nil { + v.Null = true + } else { + obj := a + tf := &v + { + t, ok := tf.AttrTypes["role"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.spec.assignments.role"}) + } else { + v, ok := tf.Attrs["role"].(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{"ScopedRoleAssignment.spec.assignments.role", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.spec.assignments.role", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Role) == "" + } + v.Value = string(obj.Role) + v.Unknown = false + tf.Attrs["role"] = v + } + } + { + t, ok := tf.AttrTypes["scope"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.spec.assignments.scope"}) + } else { + v, ok := tf.Attrs["scope"].(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{"ScopedRoleAssignment.spec.assignments.scope", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.spec.assignments.scope", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.Scope) == "" + } + v.Value = string(obj.Scope) + v.Unknown = false + tf.Attrs["scope"] = v + } + } + } + v.Unknown = false + c.Elems[k] = v + } + if len(obj.Assignments) > 0 { + c.Null = false + } + } + c.Unknown = false + tf.Attrs["assignments"] = c + } + } + } + { + t, ok := tf.AttrTypes["bot_name"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.spec.bot_name"}) + } else { + v, ok := tf.Attrs["bot_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{"ScopedRoleAssignment.spec.bot_name", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.spec.bot_name", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.BotName) == "" + } + v.Value = string(obj.BotName) + v.Unknown = false + tf.Attrs["bot_name"] = v + } + } + { + t, ok := tf.AttrTypes["bot_scope"] + if !ok { + diags.Append(attrWriteMissingDiag{"ScopedRoleAssignment.spec.bot_scope"}) + } else { + v, ok := tf.Attrs["bot_scope"].(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{"ScopedRoleAssignment.spec.bot_scope", err}) + } + v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String) + if !ok { + diags.Append(attrWriteConversionFailureDiag{"ScopedRoleAssignment.spec.bot_scope", "github.com/hashicorp/terraform-plugin-framework/types.String"}) + } + v.Null = string(obj.BotScope) == "" + } + v.Value = string(obj.BotScope) + v.Unknown = false + tf.Attrs["bot_scope"] = v + } + } + } + v.Unknown = false + tf.Attrs["spec"] = v + } + } + } + return diags +} + +// attrReadMissingDiag represents diagnostic message on an attribute missing in the source object +type attrReadMissingDiag struct { + Path string +} + +func (d attrReadMissingDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrReadMissingDiag) Summary() string { + return "Error reading from Terraform object" +} + +func (d attrReadMissingDiag) Detail() string { + return fmt.Sprintf("A value for %v is missing in the source Terraform object Attrs", d.Path) +} + +func (d attrReadMissingDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrReadConversionFailureDiag represents diagnostic message on a failed type conversion on read +type attrReadConversionFailureDiag struct { + Path string + Type string +} + +func (d attrReadConversionFailureDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrReadConversionFailureDiag) Summary() string { + return "Error reading from Terraform object" +} + +func (d attrReadConversionFailureDiag) Detail() string { + return fmt.Sprintf("A value for %v can not be converted to %v", d.Path, d.Type) +} + +func (d attrReadConversionFailureDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteMissingDiag represents diagnostic message on an attribute missing in the target object +type attrWriteMissingDiag struct { + Path string +} + +func (d attrWriteMissingDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteMissingDiag) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteMissingDiag) Detail() string { + return fmt.Sprintf("A value for %v is missing in the source Terraform object AttrTypes", d.Path) +} + +func (d attrWriteMissingDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteConversionFailureDiag represents diagnostic message on a failed type conversion on write +type attrWriteConversionFailureDiag struct { + Path string + Type string +} + +func (d attrWriteConversionFailureDiag) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteConversionFailureDiag) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteConversionFailureDiag) Detail() string { + return fmt.Sprintf("A value for %v can not be converted to %v", d.Path, d.Type) +} + +func (d attrWriteConversionFailureDiag) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} + +// attrWriteGeneralError represents diagnostic message on a generic error on write +type attrWriteGeneralError struct { + Path string + Err error +} + +func (d attrWriteGeneralError) Severity() github_com_hashicorp_terraform_plugin_framework_diag.Severity { + return github_com_hashicorp_terraform_plugin_framework_diag.SeverityError +} + +func (d attrWriteGeneralError) Summary() string { + return "Error writing to Terraform object" +} + +func (d attrWriteGeneralError) Detail() string { + return fmt.Sprintf("%s: %s", d.Path, d.Err.Error()) +} + +func (d attrWriteGeneralError) Equal(o github_com_hashicorp_terraform_plugin_framework_diag.Diagnostic) bool { + return (d.Severity() == o.Severity()) && (d.Summary() == o.Summary()) && (d.Detail() == o.Detail()) +} diff --git a/integrations/terraform/tfschema/scopes/access/assignment/v1/custom_types.go b/integrations/terraform/tfschema/scopes/access/assignment/v1/custom_types.go new file mode 100644 index 00000000000..6841fe6892b --- /dev/null +++ b/integrations/terraform/tfschema/scopes/access/assignment/v1/custom_types.go @@ -0,0 +1,25 @@ +/* +Copyright 2026 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 v1 + +import "github.com/gravitational/teleport/integrations/terraform/tfschema/resource153" + +var ( + GenSchemaTimestamp = resource153.GenSchemaTimestamp + CopyToTimestamp = resource153.CopyToTimestamp + CopyFromTimestamp = resource153.CopyFromTimestamp +) diff --git a/lib/services/presets.go b/lib/services/presets.go index 51903698ea4..3d3f8f9ddff 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -845,6 +845,7 @@ func NewPresetTerraformProviderRole() types.Role { types.NewRule(types.KindSAMLIdPServiceProvider, RW()), types.NewRule(types.KindScopedToken, RW()), types.NewRule(access.KindScopedRole, RW()), + types.NewRule(access.KindScopedRoleAssignment, RW()), }, }, }, diff --git a/lib/services/presets_test.go b/lib/services/presets_test.go index e56360cf994..8392ace4caa 100644 --- a/lib/services/presets_test.go +++ b/lib/services/presets_test.go @@ -780,6 +780,7 @@ func TestAddRoleDefaults(t *testing.T) { types.NewRule(types.KindRetrievalModel, RW()), types.NewRule(types.KindScopedToken, RW()), types.NewRule(access.KindScopedRole, RW()), + types.NewRule(access.KindScopedRoleAssignment, RW()), }, }, },