mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
Terraform: convert scoped resources to generic provider (#68866)
The currently supported set of scoped resources: ScopedRole, ScopedToken, ScopedRoleAssignment, KubernetesCluster, and App have all been migrated to the new generic terraform provider mechanism. This is required for those resources to properly provide the name and scope pair which uniquely identifies a scoped resource. Note: not all scoped resources currently support being identifier by a the (name, scope) pair. However, this conversion is a prereq for when they do. At such a time, those resources should only require changing the identifier type and plumbing the name and scope into the Get and Delete requests.
This commit is contained in:
@@ -299,10 +299,16 @@ func (c *Client) GetAccessListMemberV2(ctx context.Context, req *accesslistv1.Ge
|
||||
// GetStaticAccessListMember returns the specified access_list_member resource. If returns error if
|
||||
// the target access_list is not of type static.
|
||||
func (c *Client) GetStaticAccessListMember(ctx context.Context, accessList, memberName string) (*accesslist.AccessListMember, error) {
|
||||
resp, err := c.grpcClient.GetStaticAccessListMember(ctx, &accesslistv1.GetStaticAccessListMemberRequest{
|
||||
return c.GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{
|
||||
AccessList: accessList,
|
||||
MemberName: memberName,
|
||||
})
|
||||
}.Build())
|
||||
}
|
||||
|
||||
// GetStaticAccessListMemberV2 returns the specified access_list_member resource. If returns error if
|
||||
// the target access_list is not of type static.
|
||||
func (c *Client) GetStaticAccessListMemberV2(ctx context.Context, req *accesslistv1.GetStaticAccessListMemberRequest) (*accesslist.AccessListMember, error) {
|
||||
resp, err := c.grpcClient.GetStaticAccessListMember(ctx, req)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
@@ -378,10 +384,16 @@ func (c *Client) UpdateAccessListMember(ctx context.Context, member *accesslist.
|
||||
// DeleteStaticAccessListMember hard deletes the specified access_list_member. It returns error and
|
||||
// does nothing if the target access_list is not of static type.
|
||||
func (c *Client) DeleteStaticAccessListMember(ctx context.Context, accessList, memberName string) error {
|
||||
_, err := c.grpcClient.DeleteStaticAccessListMember(ctx, &accesslistv1.DeleteStaticAccessListMemberRequest{
|
||||
return c.DeleteStaticAccessListMemberV2(ctx, accesslistv1.DeleteStaticAccessListMemberRequest_builder{
|
||||
AccessList: accessList,
|
||||
MemberName: memberName,
|
||||
})
|
||||
}.Build())
|
||||
}
|
||||
|
||||
// DeleteStaticAccessListMemberV2 hard deletes the specified access_list_member. It returns error and
|
||||
// does nothing if the target access_list is not of static type.
|
||||
func (c *Client) DeleteStaticAccessListMemberV2(ctx context.Context, req *accesslistv1.DeleteStaticAccessListMemberRequest) error {
|
||||
_, err := c.grpcClient.DeleteStaticAccessListMember(ctx, req)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -52,7 +52,6 @@ Optional:
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
|
||||
|
||||
|
||||
-1
@@ -52,7 +52,6 @@ Optional:
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
|
||||
|
||||
|
||||
-1
@@ -95,7 +95,6 @@ Optional:
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
|
||||
|
||||
|
||||
-1
@@ -127,7 +127,6 @@ Optional:
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -356,21 +356,6 @@ var (
|
||||
HasCheckAndSetDefaults: true,
|
||||
}
|
||||
|
||||
kubernetesCluster = payload{
|
||||
Name: "KubeCluster",
|
||||
TypeName: "KubernetesClusterV3",
|
||||
VarName: "kubeCluster",
|
||||
GetMethod: "GetKubernetesCluster",
|
||||
CreateMethod: "CreateKubernetesCluster",
|
||||
UpdateMethod: "UpdateKubernetesCluster",
|
||||
DeleteMethod: "DeleteKubernetesCluster",
|
||||
ID: `kubeCluster.Metadata.Name`,
|
||||
Kind: "kube_cluster",
|
||||
HasStaticID: false,
|
||||
TerraformResourceType: "teleport_kube_cluster",
|
||||
HasCheckAndSetDefaults: true,
|
||||
}
|
||||
|
||||
lock = payload{
|
||||
Name: "Lock",
|
||||
TypeName: "LockV2",
|
||||
@@ -591,52 +576,6 @@ var (
|
||||
HasCheckAndSetDefaults: true,
|
||||
}
|
||||
|
||||
accessList = payload{
|
||||
Name: "AccessList",
|
||||
TypeName: "AccessList",
|
||||
VarName: "accessList",
|
||||
GetMethod: "AccessListClient().GetAccessList",
|
||||
CreateMethod: "AccessListClient().UpsertAccessList",
|
||||
UpsertMethodArity: 2,
|
||||
UpdateMethod: "AccessListClient().UpsertAccessList",
|
||||
DeleteMethod: "AccessListClient().DeleteAccessList",
|
||||
ID: "accessList.Header.Metadata.Name",
|
||||
Kind: "access_list",
|
||||
HasStaticID: false,
|
||||
SchemaPackage: "schemav1",
|
||||
SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1",
|
||||
ProtoPackage: "accesslist",
|
||||
ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1",
|
||||
TerraformResourceType: "teleport_access_list",
|
||||
ConvertPackagePath: "github.com/gravitational/teleport/api/types/accesslist/convert/v1",
|
||||
HasCheckAndSetDefaults: true,
|
||||
PropagatedFields: []string{"Spec.Audit.NextAuditDate"},
|
||||
}
|
||||
|
||||
accessListMember = payload{
|
||||
Name: "Member",
|
||||
TypeName: "Member",
|
||||
VarName: "accessListMember",
|
||||
GetMethod: "AccessListClient().GetStaticAccessListMember",
|
||||
CreateMethod: "AccessListClient().UpsertStaticAccessListMember",
|
||||
UpsertMethodArity: 2,
|
||||
UpdateMethod: "AccessListClient().UpsertStaticAccessListMember",
|
||||
DeleteMethod: "AccessListClient().DeleteStaticAccessListMember",
|
||||
IDPrefix: "accessListMember.Spec.AccessList",
|
||||
ID: "accessListMember.Header.Metadata.Name",
|
||||
Kind: "access_list_member",
|
||||
HasStaticID: false,
|
||||
SchemaPackage: "schemav1",
|
||||
SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1",
|
||||
ProtoPackage: "accesslist",
|
||||
ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1",
|
||||
TerraformResourceType: "teleport_access_list_member",
|
||||
ConvertPackagePath: "github.com/gravitational/teleport/api/types/accesslist/convert/v1",
|
||||
ConvertToProtoFunc: "ToMemberProto",
|
||||
ConvertFromProtoFunc: "FromMemberProto",
|
||||
HasCheckAndSetDefaults: true,
|
||||
}
|
||||
|
||||
server = payload{
|
||||
Name: "Server",
|
||||
TypeName: "ServerV2",
|
||||
@@ -1055,88 +994,6 @@ var (
|
||||
ForceSetKind: "apitypes.KindClassifier",
|
||||
}
|
||||
|
||||
scopedRole = payload{
|
||||
Name: "ScopedRole",
|
||||
TypeName: "ScopedRole",
|
||||
VarName: "scopedRole",
|
||||
GetMethod: "ScopedAccessServiceClient().GetScopedRole",
|
||||
CreateMethod: "ScopedAccessServiceClient().CreateScopedRole",
|
||||
UpdateMethod: "ScopedAccessServiceClient().UpsertScopedRole",
|
||||
UpsertMethodArity: 2,
|
||||
DeleteMethod: "ScopedAccessServiceClient().DeleteScopedRole",
|
||||
ID: "scopedRole.Metadata.Name",
|
||||
Kind: "scoped_role",
|
||||
HasStaticID: false,
|
||||
ProtoPackage: "accessv1",
|
||||
ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1",
|
||||
SchemaPackage: "schemav1",
|
||||
SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/v1",
|
||||
TerraformResourceType: "teleport_scoped_role",
|
||||
IsPlainStruct: true,
|
||||
ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/lib/scopes/access\""},
|
||||
ForceSetKind: "apitypes.KindScopedRole",
|
||||
RequestWrapper: &RequestWrapper{
|
||||
RequestResourceField: "Role",
|
||||
GetRequest: "GetScopedRoleRequest",
|
||||
CreateRequest: "CreateScopedRoleRequest",
|
||||
UpdateRequest: "UpsertScopedRoleRequest",
|
||||
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",
|
||||
VarName: "scopedToken",
|
||||
GetMethod: "GetScopedToken",
|
||||
CreateMethod: "CreateScopedToken",
|
||||
UpdateMethod: "UpsertScopedToken",
|
||||
UpsertMethodArity: 2,
|
||||
DeleteMethod: "DeleteScopedToken",
|
||||
ID: "scopedToken.Metadata.Name",
|
||||
Kind: "scoped_token",
|
||||
WithSecrets: "true",
|
||||
HasStaticID: false,
|
||||
ProtoPackage: "joiningv1",
|
||||
ProtoPackagePath: "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/joining/v1",
|
||||
SchemaPackage: "schemav1",
|
||||
SchemaPackagePath: "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/joining/v1",
|
||||
TerraformResourceType: "teleport_scoped_token",
|
||||
IsPlainStruct: true,
|
||||
ExtraImports: []string{"apitypes \"github.com/gravitational/teleport/api/types\""},
|
||||
ForceSetKind: "apitypes.KindScopedToken",
|
||||
}
|
||||
|
||||
workloadCluster = payload{
|
||||
Name: "WorkloadCluster",
|
||||
TypeName: "WorkloadCluster",
|
||||
@@ -1281,8 +1138,6 @@ func genTFSchema() {
|
||||
generateDataSource(dynamicWindowsDesktop, pluralDataSource)
|
||||
generateResource(githubConnector, pluralResource)
|
||||
generateDataSource(githubConnector, pluralDataSource)
|
||||
generateResource(kubernetesCluster, pluralResource)
|
||||
generateDataSource(kubernetesCluster, pluralDataSource)
|
||||
generateResource(lock, pluralResource)
|
||||
generateDataSource(lock, pluralDataSource)
|
||||
generateResource(oidcConnector, pluralResource)
|
||||
@@ -1309,10 +1164,6 @@ func genTFSchema() {
|
||||
generateDataSource(deviceTrust, pluralDataSource)
|
||||
generateResource(oktaImportRule, pluralResource)
|
||||
generateDataSource(oktaImportRule, pluralDataSource)
|
||||
generateResource(accessList, pluralResource)
|
||||
generateDataSource(accessList, pluralDataSource)
|
||||
generateResource(accessListMember, pluralResource)
|
||||
generateDataSource(accessListMember, pluralDataSource)
|
||||
generateResource(server, pluralResource)
|
||||
generateDataSource(server, pluralDataSource)
|
||||
generateResource(installer, pluralResource)
|
||||
@@ -1347,12 +1198,6 @@ func genTFSchema() {
|
||||
generateDataSource(classifier, pluralDataSource)
|
||||
generateResource(retrievalModel, singularResource)
|
||||
generateDataSource(retrievalModel, singularDataSource)
|
||||
generateResource(scopedRole, pluralResource)
|
||||
generateDataSource(scopedRole, pluralDataSource)
|
||||
generateResource(scopedRoleAssignment, pluralResource)
|
||||
generateDataSource(scopedRoleAssignment, pluralDataSource)
|
||||
generateResource(scopedToken, pluralResource)
|
||||
generateDataSource(scopedToken, pluralDataSource)
|
||||
generateResource(workloadCluster, pluralResource)
|
||||
generateDataSource(workloadCluster, pluralDataSource)
|
||||
generateResource(databaseObjectImportRule, pluralResource)
|
||||
|
||||
@@ -42,11 +42,13 @@ injected_fields:
|
||||
exclude_fields:
|
||||
# Metadata (we id resources by name on our side)
|
||||
- "AccessList.header.metadata.id"
|
||||
- "AccessList.header.metadata.revision"
|
||||
# Read only field
|
||||
- "AccessList.spec.owners.ineligible_status"
|
||||
- "AccessList.status"
|
||||
# Metadata (we id resources by name on our side)
|
||||
- "Member.header.metadata.id"
|
||||
- "Member.header.metadata.revision"
|
||||
# Read only field
|
||||
- "Member.spec.ineligible_status"
|
||||
- "Member.status"
|
||||
@@ -56,12 +58,10 @@ computed_fields:
|
||||
- "AccessList.header.kind"
|
||||
- "AccessList.header.metadata.expires"
|
||||
- "AccessList.header.metadata.namespace"
|
||||
- "AccessList.header.metadata.revision"
|
||||
- "AccessList.spec.audit.next_audit_date"
|
||||
- "Member.header.kind"
|
||||
- "Member.header.metadata.expires"
|
||||
- "Member.header.metadata.namespace"
|
||||
- "Member.header.metadata.revision"
|
||||
- "Member.spec.name"
|
||||
- "Member.spec.joined"
|
||||
- "Member.spec.added_by"
|
||||
@@ -79,6 +79,12 @@ plan_modifiers:
|
||||
# Force to recreate resource if it's ID changes
|
||||
Metadata.name:
|
||||
- "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()"
|
||||
# Scope is part of the access_list ID.
|
||||
AccessList.scope:
|
||||
- "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()"
|
||||
# Scope is part of the access_list_member ID.
|
||||
Member.scope:
|
||||
- "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()"
|
||||
# spec.access_list is a part of access_list_member ID. In the backend member's ID is
|
||||
# constructed like:
|
||||
# "{access_list_member.spec.access_list}/{access_list_member.header.metadata.name}"
|
||||
|
||||
@@ -513,6 +513,11 @@ plan_modifiers:
|
||||
# Force to recreate resource if it's name changes
|
||||
Metadata.Name:
|
||||
- "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()"
|
||||
# Scope is part of the scoped resource identity and must not be updated in-place.
|
||||
AppV3.scope:
|
||||
- "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()"
|
||||
KubernetesClusterV3.scope:
|
||||
- "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()"
|
||||
ServerV2.Metadata.Name:
|
||||
- "github.com/hashicorp/terraform-plugin-framework/tfsdk.UseStateForUnknown()"
|
||||
- "github.com/hashicorp/terraform-plugin-framework/tfsdk.RequiresReplace()"
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
// Code generated by _gen/main.go DO NOT EDIT
|
||||
/*
|
||||
Copyright 2015-2024 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
convert "github.com/gravitational/teleport/api/types/accesslist/convert/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"
|
||||
|
||||
schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
|
||||
)
|
||||
|
||||
// dataSourceTeleportAccessListType is the data source metadata type
|
||||
type dataSourceTeleportAccessListType struct{}
|
||||
|
||||
// dataSourceTeleportAccessList is the resource
|
||||
type dataSourceTeleportAccessList struct {
|
||||
p Provider
|
||||
}
|
||||
|
||||
// GetSchema returns the data source schema
|
||||
func (r dataSourceTeleportAccessListType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
|
||||
return schemav1.GenSchemaAccessList(ctx)
|
||||
}
|
||||
|
||||
// NewDataSource creates the empty data source
|
||||
func (r dataSourceTeleportAccessListType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) {
|
||||
return dataSourceTeleportAccessList{
|
||||
p: p.(Provider),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read reads teleport AccessList
|
||||
func (r dataSourceTeleportAccessList) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) {
|
||||
var id types.String
|
||||
diags := req.Config.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
accessListI, err := r.p.Client().AccessListClient().GetAccessList(ctx, id.Value)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list"))
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
accessList := convert.ToProto(accessListI)
|
||||
|
||||
diags = schemav1.CopyAccessListToTerraform(ctx, accessList, &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
|
||||
}
|
||||
}
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
// Code generated by _gen/main.go DO NOT EDIT
|
||||
/*
|
||||
Copyright 2015-2024 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
convert "github.com/gravitational/teleport/api/types/accesslist/convert/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"
|
||||
|
||||
schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
|
||||
)
|
||||
|
||||
// dataSourceTeleportMemberType is the data source metadata type
|
||||
type dataSourceTeleportMemberType struct{}
|
||||
|
||||
// dataSourceTeleportMember is the resource
|
||||
type dataSourceTeleportMember struct {
|
||||
p Provider
|
||||
}
|
||||
|
||||
// GetSchema returns the data source schema
|
||||
func (r dataSourceTeleportMemberType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
|
||||
return schemav1.GenSchemaMember(ctx)
|
||||
}
|
||||
|
||||
// NewDataSource creates the empty data source
|
||||
func (r dataSourceTeleportMemberType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) {
|
||||
return dataSourceTeleportMember{
|
||||
p: p.(Provider),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read reads teleport Member
|
||||
func (r dataSourceTeleportMember) Read(ctx context.Context, req tfsdk.ReadDataSourceRequest, resp *tfsdk.ReadDataSourceResponse) {
|
||||
var idPrefix types.String
|
||||
diags := req.Config.GetAttribute(ctx, path.Root("spec").AtName("access_list"), &idPrefix)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
var id types.String
|
||||
diags = req.Config.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
accessListMemberI, err := r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix.Value, id.Value)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
|
||||
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"] = types.String{Value: formatID(idPrefix.Value, id.Value)}
|
||||
}
|
||||
|
||||
accessListMember := convert.ToMemberProto(accessListMemberI)
|
||||
|
||||
diags = schemav1.CopyMemberToTerraform(ctx, accessListMember, &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
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Code generated by _gen/main.go DO NOT EDIT
|
||||
/*
|
||||
Copyright 2015-2024 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apitypes "github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/tfschema"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
|
||||
)
|
||||
|
||||
// dataSourceTeleportKubeClusterType is the data source metadata type
|
||||
type dataSourceTeleportKubeClusterType struct{}
|
||||
|
||||
// dataSourceTeleportKubeCluster is the resource
|
||||
type dataSourceTeleportKubeCluster struct {
|
||||
p Provider
|
||||
}
|
||||
|
||||
// GetSchema returns the data source schema
|
||||
func (r dataSourceTeleportKubeClusterType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
|
||||
return tfschema.GenSchemaKubernetesClusterV3(ctx)
|
||||
}
|
||||
|
||||
// NewDataSource creates the empty data source
|
||||
func (r dataSourceTeleportKubeClusterType) NewDataSource(_ context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) {
|
||||
return dataSourceTeleportKubeCluster{
|
||||
p: p.(Provider),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Read reads teleport KubeCluster
|
||||
func (r dataSourceTeleportKubeCluster) 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
|
||||
}
|
||||
|
||||
kubeClusterI, err := r.p.Client().GetKubernetesCluster(ctx, id.Value)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
kubeCluster := kubeClusterI.(*apitypes.KubernetesClusterV3)
|
||||
diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeCluster, &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
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// formatID takes the prefix and the name of the resource and converts it to a string ID.
|
||||
func formatID(prefix, name string) string {
|
||||
return prefix + "/" + name
|
||||
}
|
||||
|
||||
// parseID takes the ID generated by [formatID] and splits it back to the prefix and resource name.
|
||||
func parseID(id string) (prefix, name string, err error) {
|
||||
split := strings.Split(id, "/")
|
||||
if len(split) != 2 {
|
||||
return "", "", trace.BadParameter("expected id %q to have a single %q separator", id, "/")
|
||||
}
|
||||
prefix = split[0]
|
||||
name = split[1]
|
||||
if prefix == "" {
|
||||
return "", "", trace.BadParameter("expected id %q prefix to be non-empty", id)
|
||||
}
|
||||
if name == "" {
|
||||
return "", "", trace.BadParameter("expected id %q name to be non-empty", id)
|
||||
}
|
||||
return prefix, name, nil
|
||||
}
|
||||
@@ -29,7 +29,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType {
|
||||
"teleport_discovery_config": dataSourceTeleportDiscoveryConfigType{},
|
||||
"teleport_dynamic_windows_desktop": dataSourceTeleportDynamicWindowsDesktopType{},
|
||||
"teleport_github_connector": dataSourceTeleportGithubConnectorType{},
|
||||
"teleport_kube_cluster": dataSourceTeleportKubeClusterType{},
|
||||
"teleport_lock": dataSourceTeleportLockType{},
|
||||
"teleport_provision_token": dataSourceTeleportProvisionTokenType{},
|
||||
"teleport_oidc_connector": dataSourceTeleportOIDCConnectorType{},
|
||||
@@ -43,8 +42,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType {
|
||||
"teleport_login_rule": dataSourceTeleportLoginRuleType{},
|
||||
"teleport_trusted_device": dataSourceTeleportDeviceV1Type{},
|
||||
"teleport_okta_import_rule": dataSourceTeleportOktaImportRuleType{},
|
||||
"teleport_access_list": dataSourceTeleportAccessListType{},
|
||||
"teleport_access_list_member": dataSourceTeleportMemberType{},
|
||||
"teleport_installer": dataSourceTeleportInstallerType{},
|
||||
"teleport_access_monitoring_rule": dataSourceTeleportAccessMonitoringRuleType{},
|
||||
"teleport_static_host_user": dataSourceTeleportStaticHostUserType{},
|
||||
@@ -54,8 +51,6 @@ func DataSourceTypes() map[string]tfsdk.DataSourceType {
|
||||
"teleport_health_check_config": dataSourceTeleportHealthCheckConfigType{},
|
||||
"teleport_vnet_config": dataSourceTeleportVnetConfigType{},
|
||||
"teleport_integration": dataSourceTeleportIntegrationType{},
|
||||
"teleport_scoped_role": dataSourceTeleportScopedRoleType{},
|
||||
"teleport_scoped_role_assignment": dataSourceTeleportScopedRoleAssignmentType{},
|
||||
"teleport_db_object_import_rule": dataSourceTeleportDatabaseObjectImportRuleType{},
|
||||
"teleport_classifier": dataSourceTeleportClassifierType{},
|
||||
// TODO(bl-nero): Add teleport_inference_* data sources after data sources
|
||||
@@ -81,7 +76,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType {
|
||||
"teleport_discovery_config": resourceTeleportDiscoveryConfigType{},
|
||||
"teleport_dynamic_windows_desktop": resourceTeleportDynamicWindowsDesktopType{},
|
||||
"teleport_github_connector": resourceTeleportGithubConnectorType{},
|
||||
"teleport_kube_cluster": resourceTeleportKubeClusterType{},
|
||||
"teleport_lock": resourceTeleportLockType{},
|
||||
"teleport_provision_token": resourceTeleportProvisionTokenType{},
|
||||
"teleport_oidc_connector": resourceTeleportOIDCConnectorType{},
|
||||
@@ -96,8 +90,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType {
|
||||
"teleport_login_rule": resourceTeleportLoginRuleType{},
|
||||
"teleport_trusted_device": resourceTeleportDeviceV1Type{},
|
||||
"teleport_okta_import_rule": resourceTeleportOktaImportRuleType{},
|
||||
"teleport_access_list": resourceTeleportAccessListType{},
|
||||
"teleport_access_list_member": resourceTeleportMemberType{},
|
||||
"teleport_server": resourceTeleportServerType{},
|
||||
"teleport_installer": resourceTeleportInstallerType{},
|
||||
"teleport_access_monitoring_rule": resourceTeleportAccessMonitoringRuleType{},
|
||||
@@ -114,8 +106,6 @@ func ResourceTypes() map[string]tfsdk.ResourceType {
|
||||
"teleport_classifier": resourceTeleportClassifierType{},
|
||||
"teleport_retrieval_model": resourceTeleportRetrievalModelType{},
|
||||
"teleport_workload_cluster": resourceTeleportWorkloadClusterType{},
|
||||
"teleport_scoped_role": resourceTeleportScopedRoleType{},
|
||||
"teleport_scoped_role_assignment": resourceTeleportScopedRoleAssignmentType{},
|
||||
"teleport_db_object_import_rule": resourceTeleportDatabaseObjectImportRuleType{},
|
||||
"teleport_client_ip_restriction": resourceTeleportClientIPRestrictionType{},
|
||||
}
|
||||
|
||||
@@ -1,343 +0,0 @@
|
||||
// Code generated by _gen/main.go DO NOT EDIT
|
||||
/*
|
||||
Copyright 2015-2024 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
accesslist "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
convert "github.com/gravitational/teleport/api/types/accesslist/convert/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"
|
||||
|
||||
schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
|
||||
)
|
||||
|
||||
// resourceTeleportAccessListType is the resource metadata type
|
||||
type resourceTeleportAccessListType struct{}
|
||||
|
||||
// resourceTeleportAccessList is the resource
|
||||
type resourceTeleportAccessList struct {
|
||||
p Provider
|
||||
}
|
||||
|
||||
// GetSchema returns the resource schema
|
||||
func (r resourceTeleportAccessListType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
|
||||
return schemav1.GenSchemaAccessList(ctx)
|
||||
}
|
||||
|
||||
// NewResource creates the empty resource
|
||||
func (r resourceTeleportAccessListType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) {
|
||||
return resourceTeleportAccessList{
|
||||
p: p.(Provider),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Create creates the AccessList
|
||||
func (r resourceTeleportAccessList) 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
|
||||
}
|
||||
|
||||
accessList := &accesslist.AccessList{}
|
||||
diags = schemav1.CopyAccessListFromTerraform(ctx, plan, accessList)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
accessListResource, err := convert.FromProto(accessList)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.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(tfdiag.DiagFromWrappedErr("Error setting AccessList defaults", trace.Wrap(err), "access_list"))
|
||||
return
|
||||
}
|
||||
|
||||
id := accessListResource.Metadata.Name
|
||||
|
||||
_, err = r.p.Client().AccessListClient().GetAccessList(ctx, id)
|
||||
if !trace.IsNotFound(err) {
|
||||
if err == nil {
|
||||
existErr := fmt.Sprintf("AccessList exists in Teleport. Either remove it (tctl rm access_list/%v)"+
|
||||
" or import it to the existing state (terraform import teleport_access_list.%v %v)", id, id, id)
|
||||
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromErr("AccessList exists in Teleport", trace.Errorf(existErr)))
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list"))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = r.p.Client().AccessListClient().UpsertAccessList(ctx, accessListResource)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating AccessList", trace.Wrap(err), "access_list"))
|
||||
return
|
||||
}
|
||||
var accessListI = accessListResource
|
||||
// Try getting the resource until it exists.
|
||||
tries := 0
|
||||
retry, err := r.p.Retry()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
tries = tries + 1
|
||||
accessListI, err = r.p.Client().AccessListClient().GetAccessList(ctx, id)
|
||||
if trace.IsNotFound(err) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(ctx.Err()), "access_list"))
|
||||
return
|
||||
case <-retry.After():
|
||||
}
|
||||
if tries >= r.p.MaxRetries() {
|
||||
diagMessage := fmt.Sprintf("Error reading AccessList (tried %d times) - state outdated, please import resource", tries)
|
||||
resp.Diagnostics.AddError(diagMessage, "access_list")
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list"))
|
||||
return
|
||||
}
|
||||
|
||||
accessListResource = accessListI
|
||||
|
||||
accessList = convert.ToProto(accessListResource)
|
||||
|
||||
|
||||
diags = schemav1.CopyAccessListToTerraform(ctx, accessList, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
plan.Attrs["id"] = types.String{Value: accessList.Header.Metadata.Name}
|
||||
|
||||
diags = resp.State.Set(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads teleport AccessList
|
||||
func (r resourceTeleportAccessList) 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("header").AtName("metadata").AtName("name"), &id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
accessListI, err := r.p.Client().AccessListClient().GetAccessList(ctx, id.Value)
|
||||
if trace.IsNotFound(err) {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list"))
|
||||
return
|
||||
}
|
||||
accessList := convert.ToProto(accessListI)
|
||||
diags = schemav1.CopyAccessListToTerraform(ctx, accessList, &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 AccessList
|
||||
func (r resourceTeleportAccessList) 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
|
||||
}
|
||||
|
||||
accessList := &accesslist.AccessList{}
|
||||
diags = schemav1.CopyAccessListFromTerraform(ctx, plan, accessList)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
accessListResource, err := convert.FromProto(accessList)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Errorf("Can not convert %T to AccessList: %s", accessListResource, err), "access_list"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := accessListResource.CheckAndSetDefaults(); err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AccessList", err, "access_list"))
|
||||
return
|
||||
}
|
||||
name := accessListResource.Metadata.Name
|
||||
|
||||
accessListBefore, err := r.p.Client().AccessListClient().GetAccessList(ctx, name)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", err, "access_list"))
|
||||
return
|
||||
}
|
||||
accessListResource.Spec.Audit.NextAuditDate = accessListBefore.Spec.Audit.NextAuditDate
|
||||
|
||||
_, err = r.p.Client().AccessListClient().UpsertAccessList(ctx, accessListResource)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating AccessList", err, "access_list"))
|
||||
return
|
||||
}
|
||||
var accessListI = accessListResource
|
||||
|
||||
tries := 0
|
||||
retry, err := r.p.Retry()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
tries = tries + 1
|
||||
accessListI, err = r.p.Client().AccessListClient().GetAccessList(ctx, name)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", err, "access_list"))
|
||||
return
|
||||
}
|
||||
if accessListBefore.GetMetadata().Revision != accessListI.GetMetadata().Revision || false {
|
||||
break
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(ctx.Err()), "access_list"))
|
||||
return
|
||||
case <-retry.After():
|
||||
}
|
||||
if tries >= r.p.MaxRetries() {
|
||||
diagMessage := fmt.Sprintf("Error reading AccessList (tried %d times) - state outdated, please import resource", tries)
|
||||
resp.Diagnostics.AddError(diagMessage, "access_list")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
accessListResource = accessListI
|
||||
|
||||
diags = schemav1.CopyAccessListToTerraform(ctx, accessList, &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 AccessList
|
||||
func (r resourceTeleportAccessList) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) {
|
||||
var id types.String
|
||||
diags := req.State.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
err := r.p.Client().AccessListClient().DeleteAccessList(ctx, id.Value)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting AccessList", trace.Wrap(err), "access_list"))
|
||||
return
|
||||
}
|
||||
|
||||
resp.State.RemoveResource(ctx)
|
||||
}
|
||||
|
||||
// ImportState imports AccessList state
|
||||
func (r resourceTeleportAccessList) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) {
|
||||
accessList, err := r.p.Client().AccessListClient().GetAccessList(ctx, req.ID)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading AccessList", trace.Wrap(err), "access_list"))
|
||||
return
|
||||
}
|
||||
|
||||
accessListResource := convert.ToProto(accessList)
|
||||
|
||||
|
||||
var state types.Object
|
||||
|
||||
diags := resp.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
diags = schemav1.CopyAccessListToTerraform(ctx, accessListResource, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
id := accessList.Metadata.Name
|
||||
|
||||
state.Attrs["id"] = types.String{Value: id}
|
||||
|
||||
diags = resp.State.Set(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
-364
@@ -1,364 +0,0 @@
|
||||
// Code generated by _gen/main.go DO NOT EDIT
|
||||
/*
|
||||
Copyright 2015-2024 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
accesslist "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
convert "github.com/gravitational/teleport/api/types/accesslist/convert/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"
|
||||
|
||||
schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
|
||||
)
|
||||
|
||||
// resourceTeleportMemberType is the resource metadata type
|
||||
type resourceTeleportMemberType struct{}
|
||||
|
||||
// resourceTeleportMember is the resource
|
||||
type resourceTeleportMember struct {
|
||||
p Provider
|
||||
}
|
||||
|
||||
// GetSchema returns the resource schema
|
||||
func (r resourceTeleportMemberType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
|
||||
return schemav1.GenSchemaMember(ctx)
|
||||
}
|
||||
|
||||
// NewResource creates the empty resource
|
||||
func (r resourceTeleportMemberType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) {
|
||||
return resourceTeleportMember{
|
||||
p: p.(Provider),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Create creates the Member
|
||||
func (r resourceTeleportMember) 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
|
||||
}
|
||||
|
||||
accessListMember := &accesslist.Member{}
|
||||
diags = schemav1.CopyMemberFromTerraform(ctx, plan, accessListMember)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
accessListMemberResource, err := convert.FromMemberProto(accessListMember)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Errorf("Can not convert %T to Member: %s", accessListMemberResource, err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
err = accessListMemberResource.CheckAndSetDefaults()
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting Member defaults", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
|
||||
idPrefix := accessListMemberResource.Spec.AccessList
|
||||
id := accessListMemberResource.Metadata.Name
|
||||
|
||||
_, err = r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, id)
|
||||
if !trace.IsNotFound(err) {
|
||||
if err == nil {
|
||||
id := formatID(idPrefix, id)
|
||||
existErr := fmt.Sprintf("Member exists in Teleport. Either remove it (tctl rm access_list_member/%v)"+
|
||||
" or import it to the existing state (terraform import teleport_access_list_member.%v %v)", id, id, id)
|
||||
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromErr("Member exists in Teleport", trace.Errorf(existErr)))
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = r.p.Client().AccessListClient().UpsertStaticAccessListMember(ctx, accessListMemberResource)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating Member", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
var accessListMemberI = accessListMemberResource
|
||||
// Try getting the resource until it exists.
|
||||
tries := 0
|
||||
retry, err := r.p.Retry()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
tries = tries + 1
|
||||
accessListMemberI, err = r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, id)
|
||||
if trace.IsNotFound(err) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(ctx.Err()), "access_list_member"))
|
||||
return
|
||||
case <-retry.After():
|
||||
}
|
||||
if tries >= r.p.MaxRetries() {
|
||||
diagMessage := fmt.Sprintf("Error reading Member (tried %d times) - state outdated, please import resource", tries)
|
||||
resp.Diagnostics.AddError(diagMessage, "access_list_member")
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
|
||||
accessListMemberResource = accessListMemberI
|
||||
|
||||
accessListMember = convert.ToMemberProto(accessListMemberResource)
|
||||
|
||||
|
||||
diags = schemav1.CopyMemberToTerraform(ctx, accessListMember, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
plan.Attrs["id"] = types.String{Value: formatID(idPrefix, accessListMember.Header.Metadata.Name)}
|
||||
|
||||
diags = resp.State.Set(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads teleport Member
|
||||
func (r resourceTeleportMember) 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 idPrefix types.String
|
||||
diags = req.State.GetAttribute(ctx, path.Root("spec").AtName("access_list"), &idPrefix)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
var id types.String
|
||||
diags = req.State.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
accessListMemberI, err := r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix.Value, id.Value)
|
||||
if trace.IsNotFound(err) {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
accessListMember := convert.ToMemberProto(accessListMemberI)
|
||||
diags = schemav1.CopyMemberToTerraform(ctx, accessListMember, &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 Member
|
||||
func (r resourceTeleportMember) 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
|
||||
}
|
||||
|
||||
accessListMember := &accesslist.Member{}
|
||||
diags = schemav1.CopyMemberFromTerraform(ctx, plan, accessListMember)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
accessListMemberResource, err := convert.FromMemberProto(accessListMember)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Errorf("Can not convert %T to Member: %s", accessListMemberResource, err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := accessListMemberResource.CheckAndSetDefaults(); err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Member", err, "access_list_member"))
|
||||
return
|
||||
}
|
||||
idPrefix := accessListMemberResource.Spec.AccessList
|
||||
name := accessListMemberResource.Metadata.Name
|
||||
|
||||
accessListMemberBefore, err := r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, name)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", err, "access_list_member"))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = r.p.Client().AccessListClient().UpsertStaticAccessListMember(ctx, accessListMemberResource)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating Member", err, "access_list_member"))
|
||||
return
|
||||
}
|
||||
var accessListMemberI = accessListMemberResource
|
||||
|
||||
tries := 0
|
||||
retry, err := r.p.Retry()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
tries = tries + 1
|
||||
accessListMemberI, err = r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, name)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", err, "access_list_member"))
|
||||
return
|
||||
}
|
||||
if accessListMemberBefore.GetMetadata().Revision != accessListMemberI.GetMetadata().Revision || false {
|
||||
break
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(ctx.Err()), "access_list_member"))
|
||||
return
|
||||
case <-retry.After():
|
||||
}
|
||||
if tries >= r.p.MaxRetries() {
|
||||
diagMessage := fmt.Sprintf("Error reading Member (tried %d times) - state outdated, please import resource", tries)
|
||||
resp.Diagnostics.AddError(diagMessage, "access_list_member")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
accessListMemberResource = accessListMemberI
|
||||
|
||||
diags = schemav1.CopyMemberToTerraform(ctx, accessListMember, &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 Member
|
||||
func (r resourceTeleportMember) Delete(ctx context.Context, req tfsdk.DeleteResourceRequest, resp *tfsdk.DeleteResourceResponse) {
|
||||
var idPrefix types.String
|
||||
diags := req.State.GetAttribute(ctx, path.Root("spec").AtName("access_list"), &idPrefix)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
var id types.String
|
||||
diags = req.State.GetAttribute(ctx, path.Root("header").AtName("metadata").AtName("name"), &id)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
err := r.p.Client().AccessListClient().DeleteStaticAccessListMember(ctx, idPrefix.Value, id.Value)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting Member", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
|
||||
resp.State.RemoveResource(ctx)
|
||||
}
|
||||
|
||||
// ImportState imports Member state
|
||||
func (r resourceTeleportMember) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) {
|
||||
idPrefix, name, err := parseID(req.ID)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error parsing Member ID", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
accessListMember, err := r.p.Client().AccessListClient().GetStaticAccessListMember(ctx, idPrefix, name)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading Member", trace.Wrap(err), "access_list_member"))
|
||||
return
|
||||
}
|
||||
|
||||
accessListMemberResource := convert.ToMemberProto(accessListMember)
|
||||
|
||||
|
||||
var state types.Object
|
||||
|
||||
diags := resp.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
diags = schemav1.CopyMemberToTerraform(ctx, accessListMemberResource, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
id := req.ID
|
||||
|
||||
state.Attrs["id"] = types.String{Value: id}
|
||||
|
||||
diags = resp.State.Set(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,346 +0,0 @@
|
||||
// Code generated by _gen/main.go DO NOT EDIT
|
||||
/*
|
||||
Copyright 2015-2024 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
apitypes "github.com/gravitational/teleport/api/types"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/tfschema"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdiag"
|
||||
)
|
||||
|
||||
// resourceTeleportKubeClusterType is the resource metadata type
|
||||
type resourceTeleportKubeClusterType struct{}
|
||||
|
||||
// resourceTeleportKubeCluster is the resource
|
||||
type resourceTeleportKubeCluster struct {
|
||||
p Provider
|
||||
}
|
||||
|
||||
// GetSchema returns the resource schema
|
||||
func (r resourceTeleportKubeClusterType) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) {
|
||||
return tfschema.GenSchemaKubernetesClusterV3(ctx)
|
||||
}
|
||||
|
||||
// NewResource creates the empty resource
|
||||
func (r resourceTeleportKubeClusterType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) {
|
||||
return resourceTeleportKubeCluster{
|
||||
p: p.(Provider),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Create creates the KubeCluster
|
||||
func (r resourceTeleportKubeCluster) 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
|
||||
}
|
||||
|
||||
kubeCluster := &apitypes.KubernetesClusterV3{}
|
||||
diags = tfschema.CopyKubernetesClusterV3FromTerraform(ctx, plan, kubeCluster)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
kubeClusterResource := kubeCluster
|
||||
|
||||
err = kubeClusterResource.CheckAndSetDefaults()
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error setting KubeCluster defaults", trace.Wrap(err), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
id := kubeClusterResource.Metadata.Name
|
||||
|
||||
_, err = r.p.Client().GetKubernetesCluster(ctx, id)
|
||||
if !trace.IsNotFound(err) {
|
||||
if err == nil {
|
||||
existErr := fmt.Sprintf("KubeCluster exists in Teleport. Either remove it (tctl rm kube_cluster/%v)"+
|
||||
" or import it to the existing state (terraform import teleport_kube_cluster.%v %v)", id, id, id)
|
||||
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromErr("KubeCluster exists in Teleport", trace.Errorf(existErr)))
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
err = r.p.Client().CreateKubernetesCluster(ctx, kubeClusterResource)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error creating KubeCluster", trace.Wrap(err), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
// Not really an inferface, just using the same name for easier templating.
|
||||
var kubeClusterI apitypes.KubeCluster
|
||||
// Try getting the resource until it exists.
|
||||
tries := 0
|
||||
retry, err := r.p.Retry()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
tries = tries + 1
|
||||
kubeClusterI, err = r.p.Client().GetKubernetesCluster(ctx, id)
|
||||
if trace.IsNotFound(err) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(ctx.Err()), "kube_cluster"))
|
||||
return
|
||||
case <-retry.After():
|
||||
}
|
||||
if tries >= r.p.MaxRetries() {
|
||||
diagMessage := fmt.Sprintf("Error reading KubeCluster (tried %d times) - state outdated, please import resource", tries)
|
||||
resp.Diagnostics.AddError(diagMessage, "kube_cluster")
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
kubeClusterResource, ok := kubeClusterI.(*apitypes.KubernetesClusterV3)
|
||||
if !ok {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Errorf("Can not convert %T to KubernetesClusterV3", kubeClusterI), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
kubeCluster = kubeClusterResource
|
||||
|
||||
diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeCluster, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
plan.Attrs["id"] = types.String{Value: kubeCluster.Metadata.Name}
|
||||
|
||||
diags = resp.State.Set(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads teleport KubeCluster
|
||||
func (r resourceTeleportKubeCluster) 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
|
||||
}
|
||||
|
||||
kubeClusterI, err := r.p.Client().GetKubernetesCluster(ctx, id.Value)
|
||||
if trace.IsNotFound(err) {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
kubeCluster := kubeClusterI.(*apitypes.KubernetesClusterV3)
|
||||
diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeCluster, &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 KubeCluster
|
||||
func (r resourceTeleportKubeCluster) 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
|
||||
}
|
||||
|
||||
kubeCluster := &apitypes.KubernetesClusterV3{}
|
||||
diags = tfschema.CopyKubernetesClusterV3FromTerraform(ctx, plan, kubeCluster)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
kubeClusterResource := kubeCluster
|
||||
|
||||
|
||||
if err := kubeClusterResource.CheckAndSetDefaults(); err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating KubeCluster", err, "kube_cluster"))
|
||||
return
|
||||
}
|
||||
name := kubeClusterResource.Metadata.Name
|
||||
|
||||
kubeClusterBefore, err := r.p.Client().GetKubernetesCluster(ctx, name)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", err, "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
err = r.p.Client().UpdateKubernetesCluster(ctx, kubeClusterResource)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error updating KubeCluster", err, "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
// Not really an inferface, just using the same name for easier templating.
|
||||
var kubeClusterI apitypes.KubeCluster
|
||||
|
||||
tries := 0
|
||||
retry, err := r.p.Retry()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for {
|
||||
tries = tries + 1
|
||||
kubeClusterI, err = r.p.Client().GetKubernetesCluster(ctx, name)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", err, "kube_cluster"))
|
||||
return
|
||||
}
|
||||
if kubeClusterBefore.GetMetadata().Revision != kubeClusterI.GetMetadata().Revision || false {
|
||||
break
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(ctx.Err()), "kube_cluster"))
|
||||
return
|
||||
case <-retry.After():
|
||||
}
|
||||
if tries >= r.p.MaxRetries() {
|
||||
diagMessage := fmt.Sprintf("Error reading KubeCluster (tried %d times) - state outdated, please import resource", tries)
|
||||
resp.Diagnostics.AddError(diagMessage, "kube_cluster")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
kubeClusterResource, ok := kubeClusterI.(*apitypes.KubernetesClusterV3)
|
||||
if !ok {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Errorf("Can not convert %T to KubernetesClusterV3", kubeClusterI), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeCluster, &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 KubeCluster
|
||||
func (r resourceTeleportKubeCluster) 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().DeleteKubernetesCluster(ctx, id.Value)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error deleting KubernetesClusterV3", trace.Wrap(err), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
resp.State.RemoveResource(ctx)
|
||||
}
|
||||
|
||||
// ImportState imports KubeCluster state
|
||||
func (r resourceTeleportKubeCluster) ImportState(ctx context.Context, req tfsdk.ImportResourceStateRequest, resp *tfsdk.ImportResourceStateResponse) {
|
||||
kubeCluster, err := r.p.Client().GetKubernetesCluster(ctx, req.ID)
|
||||
if err != nil {
|
||||
resp.Diagnostics.Append(tfdiag.DiagFromWrappedErr("Error reading KubeCluster", trace.Wrap(err), "kube_cluster"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
kubeClusterResource := kubeCluster.(*apitypes.KubernetesClusterV3)
|
||||
|
||||
var state types.Object
|
||||
|
||||
diags := resp.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
diags = tfschema.CopyKubernetesClusterV3ToTerraform(ctx, kubeClusterResource, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
id := kubeClusterResource.GetName()
|
||||
|
||||
state.Attrs["id"] = types.String{Value: id}
|
||||
|
||||
diags = resp.State.Set(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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"
|
||||
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
apitypes "github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
convertv1 "github.com/gravitational/teleport/api/types/accesslist/convert/v1"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1"
|
||||
)
|
||||
|
||||
// NewAccessListDataSourceType returns the app data source type.
|
||||
func NewAccessListDataSourceType() tfdriver.DataSourceType[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return tfdriver.DataSourceType[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier]{
|
||||
NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return teleport.NewAccessListClient(clientFromProvider(p))
|
||||
},
|
||||
Kind: apitypes.KindAccessList,
|
||||
Codec: tfdriver.DataSourceCodecFuncs[accesslist.AccessList]{
|
||||
SchemaFunc: schemav1.GenSchemaAccessList,
|
||||
ToStateFunc: func(ctx context.Context, al *accesslist.AccessList, o *types.Object) diag.Diagnostics {
|
||||
return schemav1.CopyAccessListToTerraform(ctx, convertv1.ToProto(al), o)
|
||||
},
|
||||
},
|
||||
Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierFromPath(
|
||||
path.Root("header").AtName("metadata").AtName("name"),
|
||||
path.Root("scope"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccessListResourceType returns the app resource type.
|
||||
func NewAccessListResourceType() tfdriver.ResourceType[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return tfdriver.ResourceType[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier]{
|
||||
NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[accesslist.AccessList, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return teleport.NewAccessListClient(clientFromProvider(p))
|
||||
},
|
||||
Kind: apitypes.KindAccessList,
|
||||
Codec: tfdriver.ResourceCodecFuncs[accesslist.AccessList]{
|
||||
SchemaFunc: schemav1.GenSchemaAccessList,
|
||||
ToStateFunc: func(ctx context.Context, al *accesslist.AccessList, o *types.Object) diag.Diagnostics {
|
||||
|
||||
return schemav1.CopyAccessListToTerraform(ctx, convertv1.ToProto(al), o)
|
||||
},
|
||||
FromPlanFunc: func(ctx context.Context, o types.Object, al *accesslist.AccessList) diag.Diagnostics {
|
||||
protoACL := new(accesslistv1.AccessList)
|
||||
diags := schemav1.CopyAccessListFromTerraform(ctx, o, protoACL)
|
||||
if diags.HasError() {
|
||||
return diags
|
||||
}
|
||||
|
||||
converted, err := convertv1.FromProto(protoACL)
|
||||
if err != nil {
|
||||
diags.AddError("Error converting access list", err.Error())
|
||||
return diags
|
||||
}
|
||||
|
||||
*al = *converted
|
||||
return diags
|
||||
|
||||
},
|
||||
},
|
||||
Normalizer: tfdriver.CheckAndSetDefaults[accesslist.AccessList](),
|
||||
Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierPolicy(
|
||||
path.Root("header").AtName("metadata").AtName("name"),
|
||||
path.Root("scope"),
|
||||
func(av *accesslist.AccessList) (string, string) {
|
||||
return av.GetMetadata().Name, av.GetScope()
|
||||
},
|
||||
),
|
||||
ResourceRevision: func(st *accesslist.AccessList) string {
|
||||
return st.GetMetadata().Revision
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"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"
|
||||
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
apitypes "github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
convertv1 "github.com/gravitational/teleport/api/types/accesslist/convert/v1"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/accesslist/v1"
|
||||
)
|
||||
|
||||
// NewAccessListMemberDataSourceType returns the app data source type.
|
||||
func NewAccessListMemberDataSourceType() tfdriver.DataSourceType[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier] {
|
||||
return tfdriver.DataSourceType[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier]{
|
||||
NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier] {
|
||||
return teleport.NewAccessListMemberClient(clientFromProvider(p))
|
||||
},
|
||||
Kind: apitypes.KindAccessListMember,
|
||||
Codec: tfdriver.DataSourceCodecFuncs[accesslist.AccessListMember]{
|
||||
SchemaFunc: schemav1.GenSchemaMember,
|
||||
ToStateFunc: func(ctx context.Context, alm *accesslist.AccessListMember, o *types.Object) diag.Diagnostics {
|
||||
return schemav1.CopyMemberToTerraform(ctx, convertv1.ToMemberProto(alm), o)
|
||||
},
|
||||
},
|
||||
Identifier: tfdriver.ScopeQualifiedCompositeIdentifierFromPath(
|
||||
path.Root("spec").AtName("access_list"),
|
||||
path.Root("header").AtName("metadata").AtName("name"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccessListMemberResourceType returns the app resource type.
|
||||
func NewAccessListMemberResourceType() tfdriver.ResourceType[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier] {
|
||||
return tfdriver.ResourceType[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier]{
|
||||
NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[accesslist.AccessListMember, tfdriver.ScopeQualifiedCompositeIdentifier] {
|
||||
return teleport.NewAccessListMemberClient(clientFromProvider(p))
|
||||
},
|
||||
Kind: apitypes.KindAccessListMember,
|
||||
Codec: tfdriver.ResourceCodecFuncs[accesslist.AccessListMember]{
|
||||
SchemaFunc: schemav1.GenSchemaMember,
|
||||
ToStateFunc: func(ctx context.Context, alm *accesslist.AccessListMember, o *types.Object) diag.Diagnostics {
|
||||
return schemav1.CopyMemberToTerraform(ctx, convertv1.ToMemberProto(alm), o)
|
||||
|
||||
},
|
||||
FromPlanFunc: func(ctx context.Context, o types.Object, alm *accesslist.AccessListMember) diag.Diagnostics {
|
||||
protoMember := new(accesslistv1.Member)
|
||||
diags := schemav1.CopyMemberFromTerraform(ctx, o, protoMember)
|
||||
if diags.HasError() {
|
||||
return diags
|
||||
}
|
||||
|
||||
converted, err := convertv1.FromMemberProto(protoMember)
|
||||
if err != nil {
|
||||
diags.AddError("Error converting access list member", err.Error())
|
||||
return diags
|
||||
}
|
||||
|
||||
*alm = *converted
|
||||
return diags
|
||||
|
||||
},
|
||||
},
|
||||
Normalizer: tfdriver.CheckAndSetDefaults[accesslist.AccessListMember](),
|
||||
Identifier: tfdriver.ScopeQualifiedCompositeIdentifierPolicy(
|
||||
path.Root("spec").AtName("access_list"),
|
||||
path.Root("header").AtName("metadata").AtName("name"),
|
||||
func(av *accesslist.AccessListMember) (string, string) {
|
||||
return av.Spec.AccessList, av.GetMetadata().Name
|
||||
}),
|
||||
ResourceRevision: func(st *accesslist.AccessListMember) string {
|
||||
return st.GetMetadata().Revision
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
|
||||
apitypes "github.com/gravitational/teleport/api/types"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
"github.com/gravitational/teleport/integrations/terraform/tfschema"
|
||||
)
|
||||
|
||||
// NewKubernetesClusterDataSourceType returns the app data source type.
|
||||
func NewKubernetesClusterDataSourceType() tfdriver.DataSourceType[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return tfdriver.DataSourceType[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier]{
|
||||
NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return teleport.NewKubernetesClient(clientFromProvider(p))
|
||||
},
|
||||
Kind: apitypes.KindKubernetesCluster,
|
||||
Codec: tfdriver.DataSourceCodecFuncs[apitypes.KubernetesClusterV3]{
|
||||
SchemaFunc: tfschema.GenSchemaKubernetesClusterV3,
|
||||
ToStateFunc: tfschema.CopyKubernetesClusterV3ToTerraform,
|
||||
},
|
||||
Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierFromPath(
|
||||
path.Root("metadata").AtName("name"),
|
||||
path.Root("scope"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// NewKubernetesClusterResourceType returns the app resource type.
|
||||
func NewKubernetesClusterResourceType() tfdriver.ResourceType[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return tfdriver.ResourceType[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier]{
|
||||
NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[apitypes.KubernetesClusterV3, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return teleport.NewKubernetesClient(clientFromProvider(p))
|
||||
},
|
||||
Kind: apitypes.KindKubernetesCluster,
|
||||
Codec: tfdriver.ResourceCodecFuncs[apitypes.KubernetesClusterV3]{
|
||||
SchemaFunc: tfschema.GenSchemaKubernetesClusterV3,
|
||||
ToStateFunc: tfschema.CopyKubernetesClusterV3ToTerraform,
|
||||
FromPlanFunc: tfschema.CopyKubernetesClusterV3FromTerraform,
|
||||
},
|
||||
Normalizer: tfdriver.CheckAndSetDefaults[apitypes.KubernetesClusterV3](),
|
||||
Identifier: tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierPolicy(
|
||||
path.Root("metadata").AtName("name"),
|
||||
path.Root("scope"),
|
||||
func(av *apitypes.KubernetesClusterV3) (string, string) {
|
||||
return av.GetMetadata().Name, av.GetScope()
|
||||
}),
|
||||
ResourceRevision: func(st *apitypes.KubernetesClusterV3) string {
|
||||
return st.GetMetadata().Revision
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
|
||||
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/v1"
|
||||
)
|
||||
|
||||
// NewScopedRoleDataSourceType returns the scoped role data source type.
|
||||
func NewScopedRoleDataSourceType() tfdriver.DataSourceType[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return tfdriver.DataSourceType[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier]{
|
||||
NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return teleport.NewScopedRoleClient(clientFromProvider(p))
|
||||
},
|
||||
Identifier: tfdriver.ScopeQualifiedNameIdentifierFromPath(path.Root("metadata").AtName("name"), path.Root("scope")),
|
||||
Kind: scopedaccess.KindScopedRole,
|
||||
Codec: tfdriver.DataSourceCodecFuncs[accessv1.ScopedRole]{
|
||||
SchemaFunc: schemav1.GenSchemaScopedRole,
|
||||
ToStateFunc: schemav1.CopyScopedRoleToTerraform,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewScopedRoleResourceType returns the scoped role resource type.
|
||||
func NewScopedRoleResourceType() tfdriver.ResourceType[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return tfdriver.ResourceType[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier]{
|
||||
NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[accessv1.ScopedRole, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return teleport.NewScopedRoleClient(clientFromProvider(p))
|
||||
},
|
||||
Kind: scopedaccess.KindScopedRole,
|
||||
Codec: tfdriver.ResourceCodecFuncs[accessv1.ScopedRole]{
|
||||
SchemaFunc: schemav1.GenSchemaScopedRole,
|
||||
ToStateFunc: schemav1.CopyScopedRoleToTerraform,
|
||||
FromPlanFunc: schemav1.CopyScopedRoleFromTerraform,
|
||||
},
|
||||
Normalizer: tfdriver.ForceKind[accessv1.ScopedRole](scopedaccess.KindScopedRole),
|
||||
Identifier: tfdriver.ScopeQualifiedNameIdentifierPolicy(
|
||||
path.Root("metadata").AtName("name"),
|
||||
path.Root("scope"),
|
||||
func(st *accessv1.ScopedRole) (string, string) {
|
||||
return st.GetMetadata().GetName(), st.GetScope()
|
||||
}),
|
||||
ResourceRevision: func(st *accessv1.ScopedRole) string {
|
||||
return st.GetMetadata().GetRevision()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||
|
||||
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/teleport"
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
schemav1 "github.com/gravitational/teleport/integrations/terraform/tfschema/scopes/access/assignment/v1"
|
||||
)
|
||||
|
||||
// NewScopedRoleAssignmentDataSourceType returns the scoped token data source type.
|
||||
func NewScopedRoleAssignmentDataSourceType() tfdriver.DataSourceType[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return tfdriver.DataSourceType[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier]{
|
||||
NewDataSourceClient: func(p tfsdk.Provider) tfdriver.DataSourceClient[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return teleport.NewScopedRoleAssignmentClient(clientFromProvider(p))
|
||||
},
|
||||
Identifier: tfdriver.ScopeQualifiedNameIdentifierFromPath(path.Root("metadata").AtName("name"), path.Root("scope")),
|
||||
Kind: scopedaccess.KindScopedRoleAssignment,
|
||||
Codec: tfdriver.DataSourceCodecFuncs[accessv1.ScopedRoleAssignment]{
|
||||
SchemaFunc: schemav1.GenSchemaScopedRoleAssignment,
|
||||
ToStateFunc: schemav1.CopyScopedRoleAssignmentToTerraform,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewScopedRoleAssignmentResourceType returns the scoped token resource type.
|
||||
func NewScopedRoleAssignmentResourceType() tfdriver.ResourceType[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return tfdriver.ResourceType[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier]{
|
||||
NewResourceClient: func(p tfsdk.Provider) tfdriver.ResourceClient[accessv1.ScopedRoleAssignment, tfdriver.ScopeQualifiedNameIdentifier] {
|
||||
return teleport.NewScopedRoleAssignmentClient(clientFromProvider(p))
|
||||
},
|
||||
Kind: scopedaccess.KindScopedRoleAssignment,
|
||||
Codec: tfdriver.ResourceCodecFuncs[accessv1.ScopedRoleAssignment]{
|
||||
SchemaFunc: schemav1.GenSchemaScopedRoleAssignment,
|
||||
ToStateFunc: schemav1.CopyScopedRoleAssignmentToTerraform,
|
||||
FromPlanFunc: schemav1.CopyScopedRoleAssignmentFromTerraform,
|
||||
},
|
||||
Normalizer: tfdriver.ForceKind[accessv1.ScopedRoleAssignment](scopedaccess.KindScopedRoleAssignment),
|
||||
Identifier: tfdriver.ScopeQualifiedNameIdentifierPolicy(
|
||||
path.Root("metadata").AtName("name"),
|
||||
path.Root("scope"),
|
||||
func(st *accessv1.ScopedRoleAssignment) (string, string) {
|
||||
return st.GetMetadata().GetName(), st.GetScope()
|
||||
}),
|
||||
ResourceRevision: func(st *accessv1.ScopedRoleAssignment) string {
|
||||
return st.GetMetadata().GetRevision()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package teleport
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/client"
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
)
|
||||
|
||||
// NewAccessListClient returns an access list client.
|
||||
func NewAccessListClient(c *client.Client) AccessListClient {
|
||||
return AccessListClient{client: c}
|
||||
}
|
||||
|
||||
// AccessListClient manages access list resources.
|
||||
type AccessListClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// Get reads an access list by name.
|
||||
func (r AccessListClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*accesslist.AccessList, error) {
|
||||
al, err := r.client.AccessListClient().GetAccessListV2(ctx, &accesslistv1.GetAccessListRequest{
|
||||
Name: id.Name,
|
||||
Scope: id.Scope,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return al, nil
|
||||
}
|
||||
|
||||
// Create creates an access list.
|
||||
func (r AccessListClient) Create(ctx context.Context, al *accesslist.AccessList) error {
|
||||
if _, err := r.client.AccessListClient().UpsertAccessList(ctx, al); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrepareUpdate preserves server-computed fields that should not be reset on update.
|
||||
func (r AccessListClient) PrepareUpdate(before, after *accesslist.AccessList) error {
|
||||
after.Spec.Audit.NextAuditDate = before.Spec.Audit.NextAuditDate
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert updates an access list.
|
||||
func (r AccessListClient) Upsert(ctx context.Context, al *accesslist.AccessList) error {
|
||||
if _, err := r.client.AccessListClient().UpsertAccessList(ctx, al); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes an access list by name.
|
||||
func (r AccessListClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error {
|
||||
return trace.Wrap(r.client.AccessListClient().DeleteAccessListV2(ctx, &accesslistv1.DeleteAccessListRequest{
|
||||
Name: id.Name,
|
||||
Scope: id.Scope,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package teleport
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/client"
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
"github.com/gravitational/teleport/api/types/accesslist"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
)
|
||||
|
||||
// NewAccessListMemberClient returns an access list member client.
|
||||
func NewAccessListMemberClient(c *client.Client) AccessListMemberClient {
|
||||
return AccessListMemberClient{client: c}
|
||||
}
|
||||
|
||||
// AccessListMemberClient manages access list member resources.
|
||||
type AccessListMemberClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// Get reads an access list member by name.
|
||||
func (r AccessListMemberClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedCompositeIdentifier) (*accesslist.AccessListMember, error) {
|
||||
member, err := r.client.AccessListClient().GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{
|
||||
AccessListScope: id.Prefix.Scope,
|
||||
AccessList: id.Prefix.Name,
|
||||
MemberScope: id.Name.Scope,
|
||||
MemberName: id.Name.Name,
|
||||
}.Build())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return member, nil
|
||||
}
|
||||
|
||||
// Create creates an access list member.
|
||||
func (r AccessListMemberClient) Create(ctx context.Context, member *accesslist.AccessListMember) error {
|
||||
if _, err := r.client.AccessListClient().UpsertStaticAccessListMember(ctx, member); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert updates an access list member.
|
||||
func (r AccessListMemberClient) Upsert(ctx context.Context, member *accesslist.AccessListMember) error {
|
||||
if _, err := r.client.AccessListClient().UpsertStaticAccessListMember(ctx, member); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes an access list member by name.
|
||||
func (r AccessListMemberClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedCompositeIdentifier) error {
|
||||
return trace.Wrap(r.client.AccessListClient().DeleteStaticAccessListMemberV2(ctx, accesslistv1.DeleteStaticAccessListMemberRequest_builder{
|
||||
AccessListScope: id.Prefix.Scope,
|
||||
AccessList: id.Prefix.Name,
|
||||
MemberScope: id.Name.Scope,
|
||||
MemberName: id.Name.Name,
|
||||
}.Build()))
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package teleport
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/client"
|
||||
presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
)
|
||||
|
||||
// NewKubernetesClient returns a Teleport client to interact with Kubernetes Cluster resources.
|
||||
func NewKubernetesClient(c *client.Client) KubernetesClient {
|
||||
return KubernetesClient{client: c}
|
||||
}
|
||||
|
||||
// KubernetesClient manages Kubernetes Cluster resources in Teleport.
|
||||
type KubernetesClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// Get reads a Kubernetes Cluster by name.
|
||||
func (r KubernetesClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*types.KubernetesClusterV3, error) {
|
||||
cluster, err := r.client.GetKubeCluster(ctx, presencev1.GetKubeClusterRequest_builder{
|
||||
Name: id.Name,
|
||||
Scope: id.Scope,
|
||||
}.Build())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
clusterv3, ok := cluster.(*types.KubernetesClusterV3)
|
||||
if !ok {
|
||||
return nil, trace.BadParameter("unexpected Kubernetes cluster type: %T", cluster)
|
||||
}
|
||||
|
||||
return clusterv3, nil
|
||||
}
|
||||
|
||||
// Create creates a Kubernetes Cluster resource in Teleport.
|
||||
func (r KubernetesClient) Create(ctx context.Context, cluster *types.KubernetesClusterV3) error {
|
||||
if err := r.client.CreateKubernetesCluster(ctx, cluster); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert updates a Kubernetes Cluster resource in Teleport.
|
||||
func (r KubernetesClient) Upsert(ctx context.Context, cluster *types.KubernetesClusterV3) error {
|
||||
if err := r.client.UpdateKubernetesCluster(ctx, cluster); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a Kubernetes Cluster resource from Teleport by name.
|
||||
func (r KubernetesClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error {
|
||||
return trace.Wrap(r.client.DeleteKubeCluster(ctx, presencev1.DeleteKubeClusterRequest_builder{
|
||||
Name: id.Name,
|
||||
Scope: id.Scope,
|
||||
}.Build()))
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package teleport
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/client"
|
||||
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
)
|
||||
|
||||
// NewScopedRoleClient returns a scoped role client.
|
||||
func NewScopedRoleClient(c *client.Client) ScopedRoleClient {
|
||||
return ScopedRoleClient{client: c}
|
||||
}
|
||||
|
||||
// ScopedRoleClient manages scoped role resources.
|
||||
type ScopedRoleClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// Get reads a scoped role by name.
|
||||
func (r ScopedRoleClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*accessv1.ScopedRole, error) {
|
||||
resp, err := r.client.ScopedAccessServiceClient().GetScopedRole(ctx, &accessv1.GetScopedRoleRequest{
|
||||
Name: id.Name,
|
||||
Scope: id.Scope,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return resp.GetRole(), nil
|
||||
}
|
||||
|
||||
// Create creates a scoped role.
|
||||
func (r ScopedRoleClient) Create(ctx context.Context, role *accessv1.ScopedRole) error {
|
||||
_, err := r.client.ScopedAccessServiceClient().CreateScopedRole(ctx, &accessv1.CreateScopedRoleRequest{
|
||||
Role: role,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert updates a scoped role.
|
||||
func (r ScopedRoleClient) Upsert(ctx context.Context, role *accessv1.ScopedRole) error {
|
||||
_, err := r.client.ScopedAccessServiceClient().UpsertScopedRole(ctx, &accessv1.UpsertScopedRoleRequest{
|
||||
Role: role,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a scoped role by name.
|
||||
func (r ScopedRoleClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error {
|
||||
_, err := r.client.ScopedAccessServiceClient().DeleteScopedRole(ctx, &accessv1.DeleteScopedRoleRequest{
|
||||
Name: id.Name,
|
||||
Scope: id.Scope,
|
||||
})
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package teleport
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/client"
|
||||
accessv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/access/v1"
|
||||
"github.com/gravitational/teleport/lib/scopes/access"
|
||||
|
||||
"github.com/gravitational/teleport/integrations/terraform/provider/internal/tfdriver"
|
||||
)
|
||||
|
||||
// NewScopedRoleAssignmentClient returns a scoped role assignment client.
|
||||
func NewScopedRoleAssignmentClient(c *client.Client) ScopedRoleAssignmentClient {
|
||||
return ScopedRoleAssignmentClient{client: c}
|
||||
}
|
||||
|
||||
// ScopedRoleAssignmentClient manages scoped role assignment resources.
|
||||
type ScopedRoleAssignmentClient struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// Get reads a scoped role assignment by name.
|
||||
func (r ScopedRoleAssignmentClient) Get(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) (*accessv1.ScopedRoleAssignment, error) {
|
||||
resp, err := r.client.ScopedAccessServiceClient().GetScopedRoleAssignment(ctx, &accessv1.GetScopedRoleAssignmentRequest{
|
||||
Name: id.Name,
|
||||
Scope: id.Scope,
|
||||
SubKind: access.SubKindDynamic,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return resp.GetAssignment(), nil
|
||||
}
|
||||
|
||||
// Create creates a scoped role assignment.
|
||||
func (r ScopedRoleAssignmentClient) Create(ctx context.Context, sra *accessv1.ScopedRoleAssignment) error {
|
||||
_, err := r.client.ScopedAccessServiceClient().CreateScopedRoleAssignment(ctx, &accessv1.CreateScopedRoleAssignmentRequest{
|
||||
Assignment: sra,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upsert updates a scoped role assignment.
|
||||
func (r ScopedRoleAssignmentClient) Upsert(ctx context.Context, sra *accessv1.ScopedRoleAssignment) error {
|
||||
_, err := r.client.ScopedAccessServiceClient().UpsertScopedRoleAssignment(ctx, &accessv1.UpsertScopedRoleAssignmentRequest{
|
||||
Assignment: sra,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a scoped role assignment by name.
|
||||
func (r ScopedRoleAssignmentClient) Delete(ctx context.Context, id tfdriver.ScopeQualifiedNameIdentifier) error {
|
||||
_, err := r.client.ScopedAccessServiceClient().DeleteScopedRoleAssignment(ctx, &accessv1.DeleteScopedRoleAssignmentRequest{
|
||||
Name: id.Name,
|
||||
Scope: id.Scope,
|
||||
SubKind: access.SubKindDynamic,
|
||||
})
|
||||
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -84,6 +84,18 @@ func (n CompositeIdentifier) String() string {
|
||||
return n.Prefix + "/" + n.Name
|
||||
}
|
||||
|
||||
// ScopeQualifiedCompositeIdentifier identifies a resource by a possibly
|
||||
// scope-qualified prefix and a possibly scope-qualified name.
|
||||
type ScopeQualifiedCompositeIdentifier struct {
|
||||
Prefix ScopeQualifiedNameIdentifier
|
||||
Name ScopeQualifiedNameIdentifier
|
||||
}
|
||||
|
||||
// String returns the Terraform import ID.
|
||||
func (n ScopeQualifiedCompositeIdentifier) String() string {
|
||||
return n.Prefix.String() + "/" + n.Name.String()
|
||||
}
|
||||
|
||||
// SingletonIdentifier identifies a resource with a fixed name.
|
||||
type SingletonIdentifier struct {
|
||||
Name string
|
||||
@@ -143,6 +155,16 @@ func NewScopedQualifiedNameIdentifier(s string) (ScopeQualifiedNameIdentifier, e
|
||||
return ScopeQualifiedNameIdentifier{Name: sqn.Name, Scope: sqn.Scope}, nil
|
||||
}
|
||||
|
||||
// NewPossiblyUnscopedScopeQualifiedNameIdentifier parses an identifier that may
|
||||
// be either a scope-qualified name or an unscoped bare name.
|
||||
func NewPossiblyUnscopedScopeQualifiedNameIdentifier(s string) (ScopeQualifiedNameIdentifier, error) {
|
||||
if strings.Contains(s, scopes.QualifiedNameSeparator) {
|
||||
return NewScopedQualifiedNameIdentifier(s)
|
||||
}
|
||||
|
||||
return ScopeQualifiedNameIdentifier{Name: s}, nil
|
||||
}
|
||||
|
||||
// ScopeQualifiedNameIdentifierFromPath returns a scope qualified name extractor.
|
||||
func ScopeQualifiedNameIdentifierFromPath(namePath, scopePath path.Path) TerraformIdentifierExtractor[ScopeQualifiedNameIdentifier] {
|
||||
return func(ctx context.Context, reader TerraformAttributeReader) (ScopeQualifiedNameIdentifier, diag.Diagnostics) {
|
||||
@@ -168,6 +190,36 @@ func ScopeQualifiedNameIdentifierFromPath(namePath, scopePath path.Path) Terrafo
|
||||
}
|
||||
}
|
||||
|
||||
// PossiblyUnscopedScopeQualifiedNameIdentifierFromPath returns an extractor for
|
||||
// identifiers that may be either scope-qualified or unscoped.
|
||||
func PossiblyUnscopedScopeQualifiedNameIdentifierFromPath(namePath, scopePath path.Path) TerraformIdentifierExtractor[ScopeQualifiedNameIdentifier] {
|
||||
return func(ctx context.Context, reader TerraformAttributeReader) (ScopeQualifiedNameIdentifier, diag.Diagnostics) {
|
||||
var identifier types.String
|
||||
diags := reader.GetAttribute(ctx, namePath, &identifier)
|
||||
if diags.HasError() {
|
||||
return ScopeQualifiedNameIdentifier{}, diags
|
||||
}
|
||||
|
||||
var scope types.String
|
||||
diags.Append(reader.GetAttribute(ctx, scopePath, &scope)...)
|
||||
if diags.HasError() {
|
||||
return ScopeQualifiedNameIdentifier{}, diags
|
||||
}
|
||||
|
||||
if scope.Null || scope.Unknown || scope.Value == "" {
|
||||
return ScopeQualifiedNameIdentifier{Name: identifier.Value}, diags
|
||||
}
|
||||
|
||||
sqn := scopes.QualifiedName{Name: identifier.Value, Scope: scope.Value}
|
||||
if err := sqn.WeakValidate(); err != nil {
|
||||
diags.Append(tfdiag.DiagFromErr("malformed scope qualified name", err))
|
||||
return ScopeQualifiedNameIdentifier{}, diags
|
||||
}
|
||||
|
||||
return ScopeQualifiedNameIdentifier{Name: sqn.Name, Scope: sqn.Scope}, diags
|
||||
}
|
||||
}
|
||||
|
||||
// ScopeQualifiedNameIdentifierPolicy returns a policy for scope qualified names.
|
||||
func ScopeQualifiedNameIdentifierPolicy[T any](namePath, scopePath path.Path, resourceNameAndScope func(*T) (name, scope string)) IdentifierPolicy[T, ScopeQualifiedNameIdentifier] {
|
||||
return IdentifierPolicy[T, ScopeQualifiedNameIdentifier]{
|
||||
@@ -180,6 +232,19 @@ func ScopeQualifiedNameIdentifierPolicy[T any](namePath, scopePath path.Path, re
|
||||
}
|
||||
}
|
||||
|
||||
// PossiblyUnscopedScopeQualifiedNameIdentifierPolicy returns a policy for
|
||||
// identifiers that may be either scope-qualified or unscoped.
|
||||
func PossiblyUnscopedScopeQualifiedNameIdentifierPolicy[T any](namePath, scopePath path.Path, resourceNameAndScope func(*T) (name, scope string)) IdentifierPolicy[T, ScopeQualifiedNameIdentifier] {
|
||||
return IdentifierPolicy[T, ScopeQualifiedNameIdentifier]{
|
||||
FromState: PossiblyUnscopedScopeQualifiedNameIdentifierFromPath(namePath, scopePath),
|
||||
FromResource: func(resource *T) ScopeQualifiedNameIdentifier {
|
||||
name, scope := resourceNameAndScope(resource)
|
||||
return ScopeQualifiedNameIdentifier{Name: name, Scope: scope}
|
||||
},
|
||||
FromImportID: NewPossiblyUnscopedScopeQualifiedNameIdentifier,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCompositeIdentifier parses a composite identifier.
|
||||
func NewCompositeIdentifier(s string) (CompositeIdentifier, error) {
|
||||
split := strings.Split(s, "/")
|
||||
@@ -231,6 +296,130 @@ func CompositeIdentifierPolicy[T any](prefixPath, namePath path.Path, resourcePr
|
||||
}
|
||||
}
|
||||
|
||||
// NewScopeQualifiedCompositeIdentifier parses a scope-qualified composite
|
||||
// identifier in "prefix/name" form, where either side may be a scope-qualified
|
||||
// name. For example: "access-list/alice", "/scope::access-list/alice", or
|
||||
// "/scope::access-list//scope::child-list".
|
||||
func NewScopeQualifiedCompositeIdentifier(s string) (ScopeQualifiedCompositeIdentifier, error) {
|
||||
prefix, name, err := splitScopeQualifiedCompositeIdentifier(s)
|
||||
if err != nil {
|
||||
return ScopeQualifiedCompositeIdentifier{}, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return newScopeQualifiedCompositeIdentifier(prefix, name)
|
||||
}
|
||||
|
||||
func splitScopeQualifiedCompositeIdentifier(s string) (prefix, name string, err error) {
|
||||
if strings.HasPrefix(s, "/") {
|
||||
scope, rest, ok := strings.Cut(s, scopes.QualifiedNameSeparator)
|
||||
if !ok {
|
||||
return "", "", trace.BadParameter("expected scoped composite id %q prefix to contain %q", s, scopes.QualifiedNameSeparator)
|
||||
}
|
||||
|
||||
prefixName, name, ok := strings.Cut(rest, "/")
|
||||
if !ok {
|
||||
return "", "", trace.BadParameter("expected id %q to have a %q separator after prefix", s, "/")
|
||||
}
|
||||
return scope + scopes.QualifiedNameSeparator + prefixName, name, nil
|
||||
}
|
||||
|
||||
prefix, name, ok := strings.Cut(s, "/")
|
||||
if !ok {
|
||||
return "", "", trace.BadParameter("expected id %q to have a %q separator", s, "/")
|
||||
}
|
||||
|
||||
if strings.Contains(name, "/") && !strings.HasPrefix(name, "/") {
|
||||
return "", "", trace.BadParameter("expected unscoped member name in id %q not to contain %q", s, "/")
|
||||
}
|
||||
|
||||
return prefix, name, nil
|
||||
}
|
||||
|
||||
func newScopeQualifiedCompositeIdentifier(prefix, name string) (ScopeQualifiedCompositeIdentifier, error) {
|
||||
if prefix == "" {
|
||||
return ScopeQualifiedCompositeIdentifier{}, trace.BadParameter("prefix must be non-empty")
|
||||
}
|
||||
if name == "" {
|
||||
return ScopeQualifiedCompositeIdentifier{}, trace.BadParameter("name must be non-empty")
|
||||
}
|
||||
|
||||
prefixSQN, err := NewPossiblyUnscopedScopeQualifiedNameIdentifier(prefix)
|
||||
if err != nil {
|
||||
return ScopeQualifiedCompositeIdentifier{}, trace.Wrap(err)
|
||||
}
|
||||
|
||||
nameSQN, err := NewPossiblyUnscopedScopeQualifiedNameIdentifier(name)
|
||||
if err != nil {
|
||||
return ScopeQualifiedCompositeIdentifier{}, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return ScopeQualifiedCompositeIdentifier{Prefix: prefixSQN, Name: nameSQN}, nil
|
||||
}
|
||||
|
||||
// ScopeQualifiedCompositeIdentifierFromPath returns an extractor for a
|
||||
// scope-qualified composite identifier.
|
||||
func ScopeQualifiedCompositeIdentifierFromPath(prefixPath, namePath path.Path) TerraformIdentifierExtractor[ScopeQualifiedCompositeIdentifier] {
|
||||
return func(ctx context.Context, reader TerraformAttributeReader) (ScopeQualifiedCompositeIdentifier, diag.Diagnostics) {
|
||||
var prefix types.String
|
||||
diags := reader.GetAttribute(ctx, prefixPath, &prefix)
|
||||
if diags.HasError() {
|
||||
return ScopeQualifiedCompositeIdentifier{}, diags
|
||||
}
|
||||
|
||||
var name types.String
|
||||
diags.Append(reader.GetAttribute(ctx, namePath, &name)...)
|
||||
if diags.HasError() {
|
||||
return ScopeQualifiedCompositeIdentifier{}, diags
|
||||
}
|
||||
|
||||
prefixSQN, err := scopeQualifiedNameIdentifierFromPossiblyQualifiedString(prefix.Value)
|
||||
if err != nil {
|
||||
diags.Append(tfdiag.DiagFromErr("malformed scope qualified prefix", err))
|
||||
return ScopeQualifiedCompositeIdentifier{}, diags
|
||||
}
|
||||
|
||||
nameSQN, err := scopeQualifiedNameIdentifierFromPossiblyQualifiedString(name.Value)
|
||||
if err != nil {
|
||||
diags.Append(tfdiag.DiagFromErr("malformed scope qualified name", err))
|
||||
return ScopeQualifiedCompositeIdentifier{}, diags
|
||||
}
|
||||
|
||||
return ScopeQualifiedCompositeIdentifier{Prefix: prefixSQN, Name: nameSQN}, diags
|
||||
}
|
||||
}
|
||||
|
||||
func scopeQualifiedNameIdentifierFromPossiblyQualifiedString(s string) (ScopeQualifiedNameIdentifier, error) {
|
||||
if !strings.Contains(s, scopes.QualifiedNameSeparator) {
|
||||
return ScopeQualifiedNameIdentifier{Name: s}, nil
|
||||
}
|
||||
|
||||
sqn, err := scopes.ParseQualifiedName(s)
|
||||
if err != nil {
|
||||
return ScopeQualifiedNameIdentifier{}, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := sqn.WeakValidate(); err != nil {
|
||||
return ScopeQualifiedNameIdentifier{}, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return ScopeQualifiedNameIdentifier{Name: sqn.Name, Scope: sqn.Scope}, nil
|
||||
}
|
||||
|
||||
// ScopeQualifiedCompositeIdentifierPolicy returns a policy for scope-qualified
|
||||
// composite identifiers.
|
||||
func ScopeQualifiedCompositeIdentifierPolicy[T any](prefixPath, namePath path.Path, resourcePrefixAndName func(*T) (prefix, name string)) IdentifierPolicy[T, ScopeQualifiedCompositeIdentifier] {
|
||||
return IdentifierPolicy[T, ScopeQualifiedCompositeIdentifier]{
|
||||
FromState: ScopeQualifiedCompositeIdentifierFromPath(prefixPath, namePath),
|
||||
FromResource: func(resource *T) ScopeQualifiedCompositeIdentifier {
|
||||
prefix, name := resourcePrefixAndName(resource)
|
||||
prefixID, _ := scopeQualifiedNameIdentifierFromPossiblyQualifiedString(prefix)
|
||||
nameID, _ := scopeQualifiedNameIdentifierFromPossiblyQualifiedString(name)
|
||||
return ScopeQualifiedCompositeIdentifier{Prefix: prefixID, Name: nameID}
|
||||
},
|
||||
FromImportID: NewScopeQualifiedCompositeIdentifier,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSingletonIdentifier returns a singleton identifier.
|
||||
func NewSingletonIdentifier(s string) (SingletonIdentifier, error) {
|
||||
return SingletonIdentifier{Name: s}, nil
|
||||
|
||||
@@ -191,6 +191,100 @@ func TestScopeQualifiedNameIdentifierFromPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPossiblyUnscopedScopeQualifiedNameIdentifierFromPath(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
attributer attributeReaderFunc
|
||||
expectedIdentifier tfdriver.ScopeQualifiedNameIdentifier
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "unscoped empty scope",
|
||||
attributer: func(p path.Path, v any) diag.Diagnostics {
|
||||
s, ok := v.(*types.String)
|
||||
if !ok {
|
||||
return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))}
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.Equal(path.Root("name")):
|
||||
s.Value = "testing"
|
||||
case p.Equal(path.Root("scope")):
|
||||
s.Value = ""
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing"},
|
||||
},
|
||||
{
|
||||
name: "unscoped null scope",
|
||||
attributer: func(p path.Path, v any) diag.Diagnostics {
|
||||
s, ok := v.(*types.String)
|
||||
if !ok {
|
||||
return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))}
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.Equal(path.Root("name")):
|
||||
s.Value = "testing"
|
||||
case p.Equal(path.Root("scope")):
|
||||
s.Null = true
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing"},
|
||||
},
|
||||
{
|
||||
name: "scoped",
|
||||
attributer: func(p path.Path, v any) diag.Diagnostics {
|
||||
s, ok := v.(*types.String)
|
||||
if !ok {
|
||||
return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))}
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.Equal(path.Root("name")):
|
||||
s.Value = "testing"
|
||||
case p.Equal(path.Root("scope")):
|
||||
s.Value = "/foo/bar"
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing", Scope: "/foo/bar"},
|
||||
},
|
||||
{
|
||||
name: "invalid scoped",
|
||||
attributer: func(p path.Path, v any) diag.Diagnostics {
|
||||
s, ok := v.(*types.String)
|
||||
if !ok {
|
||||
return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))}
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.Equal(path.Root("name")):
|
||||
s.Value = "testing"
|
||||
case p.Equal(path.Root("scope")):
|
||||
s.Value = "/foo/bar&[]/"
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
identifier, d := tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierFromPath(path.Root("name"), path.Root("scope"))(t.Context(), test.attributer)
|
||||
assert.Equal(t, test.expectError, d.HasError())
|
||||
assert.Equal(t, test.expectedIdentifier, identifier)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewScopeQualifiedNameIdentifier(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -226,6 +320,73 @@ func TestNewScopeQualifiedNameIdentifier(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestNewPossiblyUnscopedScopeQualifiedNameIdentifier(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
errorAssertion require.ErrorAssertionFunc
|
||||
expectedIdentifier tfdriver.ScopeQualifiedNameIdentifier
|
||||
}{
|
||||
{
|
||||
name: "unscoped",
|
||||
input: "testing",
|
||||
errorAssertion: require.NoError,
|
||||
expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing"},
|
||||
},
|
||||
{
|
||||
name: "scoped",
|
||||
input: "/animals/llama::testing",
|
||||
errorAssertion: require.NoError,
|
||||
expectedIdentifier: tfdriver.ScopeQualifiedNameIdentifier{Name: "testing", Scope: "/animals/llama"},
|
||||
},
|
||||
{
|
||||
name: "invalid scoped",
|
||||
input: "/animals/llama::testing&[]/",
|
||||
errorAssertion: require.Error,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
identifier, err := tfdriver.NewPossiblyUnscopedScopeQualifiedNameIdentifier(test.input)
|
||||
test.errorAssertion(t, err)
|
||||
require.Equal(t, test.expectedIdentifier, identifier)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPossiblyUnscopedScopeQualifiedNameIdentifierPolicy(t *testing.T) {
|
||||
type testResource struct {
|
||||
Name string
|
||||
Scope string
|
||||
}
|
||||
|
||||
policy := tfdriver.PossiblyUnscopedScopeQualifiedNameIdentifierPolicy(
|
||||
path.Root("name"),
|
||||
path.Root("scope"),
|
||||
func(resource *testResource) (string, string) {
|
||||
return resource.Name, resource.Scope
|
||||
},
|
||||
)
|
||||
|
||||
require.Equal(t,
|
||||
tfdriver.ScopeQualifiedNameIdentifier{Name: "unscoped"},
|
||||
policy.FromResource(&testResource{Name: "unscoped"}),
|
||||
)
|
||||
require.Equal(t,
|
||||
tfdriver.ScopeQualifiedNameIdentifier{Name: "scoped", Scope: "/foo/bar"},
|
||||
policy.FromResource(&testResource{Name: "scoped", Scope: "/foo/bar"}),
|
||||
)
|
||||
|
||||
identifier, err := policy.FromImportID("unscoped")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tfdriver.ScopeQualifiedNameIdentifier{Name: "unscoped"}, identifier)
|
||||
|
||||
identifier, err = policy.FromImportID("/foo/bar::scoped")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tfdriver.ScopeQualifiedNameIdentifier{Name: "scoped", Scope: "/foo/bar"}, identifier)
|
||||
}
|
||||
|
||||
func TestCompositeIdentifierFromPath(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -424,6 +585,64 @@ func TestCompositeIdentifierPolicy(t *testing.T) {
|
||||
require.Equal(t, tfdriver.CompositeIdentifier{Prefix: "import-prefix", Name: "import-name"}, fromImport)
|
||||
}
|
||||
|
||||
func TestScopeQualifiedCompositeIdentifierPolicy(t *testing.T) {
|
||||
type resource struct {
|
||||
prefix string
|
||||
name string
|
||||
}
|
||||
policy := tfdriver.ScopeQualifiedCompositeIdentifierPolicy(
|
||||
path.Root("prefix"),
|
||||
path.Root("name"),
|
||||
func(r *resource) (prefix, name string) {
|
||||
return r.prefix, r.name
|
||||
},
|
||||
)
|
||||
|
||||
fromState, diags := policy.FromState(t.Context(), attributeReaderFunc(func(p path.Path, v any) diag.Diagnostics {
|
||||
s, ok := v.(*types.String)
|
||||
if !ok {
|
||||
return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("expected type string, but got %T", v))}
|
||||
}
|
||||
switch {
|
||||
case p.Equal(path.Root("prefix")):
|
||||
s.Value = "/state/scope::state-prefix"
|
||||
case p.Equal(path.Root("name")):
|
||||
s.Value = "/state/scope::state-name"
|
||||
default:
|
||||
return diag.Diagnostics{diag.NewErrorDiagnostic("fail", fmt.Sprintf("unexpected path %s", p.String()))}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
require.False(t, diags.HasError(), diags)
|
||||
require.Equal(t, tfdriver.ScopeQualifiedCompositeIdentifier{
|
||||
Prefix: tfdriver.ScopeQualifiedNameIdentifier{Name: "state-prefix", Scope: "/state/scope"},
|
||||
Name: tfdriver.ScopeQualifiedNameIdentifier{Name: "state-name", Scope: "/state/scope"},
|
||||
}, fromState)
|
||||
require.Equal(t, "/state/scope::state-prefix//state/scope::state-name", fromState.String())
|
||||
|
||||
fromResource := policy.FromResource(&resource{prefix: "/resource/scope::resource-prefix", name: "resource-name"})
|
||||
require.Equal(t, tfdriver.ScopeQualifiedCompositeIdentifier{
|
||||
Prefix: tfdriver.ScopeQualifiedNameIdentifier{Name: "resource-prefix", Scope: "/resource/scope"},
|
||||
Name: tfdriver.ScopeQualifiedNameIdentifier{Name: "resource-name"},
|
||||
}, fromResource)
|
||||
require.Equal(t, "/resource/scope::resource-prefix/resource-name", fromResource.String())
|
||||
|
||||
fromImport, err := policy.FromImportID("/import/scope::import-prefix//import/scope::import-name")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tfdriver.ScopeQualifiedCompositeIdentifier{
|
||||
Prefix: tfdriver.ScopeQualifiedNameIdentifier{Name: "import-prefix", Scope: "/import/scope"},
|
||||
Name: tfdriver.ScopeQualifiedNameIdentifier{Name: "import-name", Scope: "/import/scope"},
|
||||
}, fromImport)
|
||||
|
||||
legacyImport, err := policy.FromImportID("import-prefix/import-name")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tfdriver.ScopeQualifiedCompositeIdentifier{
|
||||
Prefix: tfdriver.ScopeQualifiedNameIdentifier{Name: "import-prefix"},
|
||||
Name: tfdriver.ScopeQualifiedNameIdentifier{Name: "import-name"},
|
||||
}, legacyImport)
|
||||
require.Equal(t, "import-prefix/import-name", legacyImport.String())
|
||||
}
|
||||
|
||||
func TestScopeQualifiedNameIdentifierPolicy(t *testing.T) {
|
||||
type resource struct {
|
||||
name string
|
||||
|
||||
@@ -564,8 +564,13 @@ func (p *Provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceTyp
|
||||
resourceTypes := legacy.ResourceTypes()
|
||||
|
||||
genericResourceTypes := map[string]tfsdk.ResourceType{
|
||||
"teleport_app": resources.NewAppResourceType(),
|
||||
"teleport_scoped_token": resources.NewScopedTokenResourceType(),
|
||||
"teleport_access_list": resources.NewAccessListResourceType(),
|
||||
"teleport_access_list_member": resources.NewAccessListMemberResourceType(),
|
||||
"teleport_app": resources.NewAppResourceType(),
|
||||
"teleport_kube_cluster": resources.NewKubernetesClusterResourceType(),
|
||||
"teleport_scoped_role": resources.NewScopedRoleResourceType(),
|
||||
"teleport_scoped_role_assignment": resources.NewScopedRoleAssignmentResourceType(),
|
||||
"teleport_scoped_token": resources.NewScopedTokenResourceType(),
|
||||
}
|
||||
|
||||
maps.Insert(resourceTypes, maps.All(genericResourceTypes))
|
||||
@@ -578,8 +583,13 @@ func (p *Provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourc
|
||||
dataSourceTypes := legacy.DataSourceTypes()
|
||||
|
||||
genericDataSourceTypes := map[string]tfsdk.DataSourceType{
|
||||
"teleport_app": resources.NewAppDataSourceType(),
|
||||
"teleport_scoped_token": resources.NewScopedTokenDataSourceType(),
|
||||
"teleport_access_list": resources.NewAccessListDataSourceType(),
|
||||
"teleport_access_list_member": resources.NewAccessListMemberDataSourceType(),
|
||||
"teleport_app": resources.NewAppDataSourceType(),
|
||||
"teleport_kube_cluster": resources.NewKubernetesClusterDataSourceType(),
|
||||
"teleport_scoped_role": resources.NewScopedRoleDataSourceType(),
|
||||
"teleport_scoped_role_assignment": resources.NewScopedRoleAssignmentDataSourceType(),
|
||||
"teleport_scoped_token": resources.NewScopedTokenDataSourceType(),
|
||||
}
|
||||
|
||||
maps.Insert(dataSourceTypes, maps.All(genericDataSourceTypes))
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package testlib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/require"
|
||||
|
||||
"github.com/gravitational/teleport/api/client/accesslist"
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
)
|
||||
|
||||
func checkAccessListMemberExists(ctx context.Context, clt *accesslist.Client, accessList, memberName string, expires time.Time) resource.TestCheckFunc {
|
||||
return checkAccessListMemberExistsWithScopes(ctx, clt, "", accessList, "", memberName, "", accessList, expires)
|
||||
}
|
||||
|
||||
func checkAccessListMemberExistsWithScopes(ctx context.Context, clt *accesslist.Client, accessListScope, accessListName, memberScope, memberName, expectedScope, expectedAccessList string, expires time.Time) resource.TestCheckFunc {
|
||||
return func(state *terraform.State) error {
|
||||
member, err := clt.GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{
|
||||
AccessListScope: accessListScope,
|
||||
AccessList: accessListName,
|
||||
MemberScope: memberScope,
|
||||
MemberName: memberName,
|
||||
}.Build())
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if member.GetName() != memberName {
|
||||
return trace.CompareFailed("member name mismatch: got %q, want %q", member.GetName(), memberName)
|
||||
}
|
||||
if member.Spec.Name != memberName {
|
||||
return trace.CompareFailed("member spec name mismatch: got %q, want %q", member.Spec.Name, memberName)
|
||||
}
|
||||
if member.Scope != expectedScope {
|
||||
return trace.CompareFailed("member scope mismatch: got %q, want %q", member.Scope, expectedScope)
|
||||
}
|
||||
if member.Spec.AccessList != expectedAccessList {
|
||||
return trace.CompareFailed("member access list mismatch: got %q, want %q", member.Spec.AccessList, expectedAccessList)
|
||||
}
|
||||
if !member.Spec.Expires.Equal(expires) {
|
||||
return trace.CompareFailed("member expiry mismatch: got %q, want %q", member.Spec.Expires, expires)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func checkScopedListAccessListMemberExists(ctx context.Context, clt *accesslist.Client, scope, accessListName, memberName string, expires time.Time) resource.TestCheckFunc {
|
||||
return func(state *terraform.State) error {
|
||||
member, err := clt.GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{
|
||||
AccessListScope: scope,
|
||||
AccessList: accessListName,
|
||||
MemberScope: scope,
|
||||
MemberName: memberName,
|
||||
}.Build())
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
accessListSQN := scopes.QualifiedName{Scope: scope, Name: accessListName}.String()
|
||||
memberSQN := scopes.QualifiedName{Scope: scope, Name: memberName}.String()
|
||||
if member.GetName() != memberSQN {
|
||||
return trace.CompareFailed("member name mismatch: got %q, want %q", member.GetName(), memberSQN)
|
||||
}
|
||||
if member.Spec.Name != memberSQN {
|
||||
return trace.CompareFailed("member spec name mismatch: got %q, want %q", member.Spec.Name, memberSQN)
|
||||
}
|
||||
if member.Scope != scope {
|
||||
return trace.CompareFailed("member scope mismatch: got %q, want %q", member.Scope, scope)
|
||||
}
|
||||
if member.Spec.AccessList != accessListSQN {
|
||||
return trace.CompareFailed("member access list mismatch: got %q, want %q", member.Spec.AccessList, accessListSQN)
|
||||
}
|
||||
if member.Spec.MembershipKind != "MEMBERSHIP_KIND_SCOPED_LIST" {
|
||||
return trace.CompareFailed("member kind mismatch: got %q, want %q", member.Spec.MembershipKind, "MEMBERSHIP_KIND_SCOPED_LIST")
|
||||
}
|
||||
if !member.Spec.Expires.Equal(expires) {
|
||||
return trace.CompareFailed("member expiry mismatch: got %q, want %q", member.Spec.Expires, expires)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TerraformSuiteEnterprise) TestAccessListMember() {
|
||||
require.True(s.T(),
|
||||
s.teleportFeatures.GetAdvancedAccessWorkflows(),
|
||||
"Test requires Advanced Access Workflows",
|
||||
)
|
||||
|
||||
ctx := s.T().Context()
|
||||
accessListName := "test-member-list"
|
||||
memberName := "fighter"
|
||||
resourceName := "teleport_access_list_member.fighter"
|
||||
|
||||
checkDestroyed := func(state *terraform.State) error {
|
||||
_, err := s.client.AccessListClient().GetStaticAccessListMember(ctx, accessListName, memberName)
|
||||
if trace.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
createdExpires := time.Date(2038, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedExpires := time.Date(2038, 2, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
resource.Test(s.T(), resource.TestCase{
|
||||
ProtoV6ProviderFactories: s.terraformProviders,
|
||||
CheckDestroy: checkDestroyed,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: s.getFixture("access_list_member_0_create.tf"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(resourceName, "header.metadata.name", memberName),
|
||||
resource.TestCheckResourceAttr(resourceName, "spec.access_list", accessListName),
|
||||
resource.TestCheckResourceAttr(resourceName, "spec.membership_kind", "1"),
|
||||
resource.TestCheckResourceAttr(resourceName, "spec.expires", createdExpires.Format(time.RFC3339)),
|
||||
checkAccessListMemberExists(ctx, s.client.AccessListClient(), accessListName, memberName, createdExpires),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: s.getFixture("access_list_member_0_create.tf"),
|
||||
PlanOnly: true,
|
||||
},
|
||||
{
|
||||
Config: s.getFixture("access_list_member_1_update.tf"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(resourceName, "header.metadata.labels.class", "veteran"),
|
||||
resource.TestCheckResourceAttr(resourceName, "spec.expires", updatedExpires.Format(time.RFC3339)),
|
||||
checkAccessListMemberExists(ctx, s.client.AccessListClient(), accessListName, memberName, updatedExpires),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: s.getFixture("access_list_member_1_update.tf"),
|
||||
PlanOnly: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *TerraformSuiteEnterpriseScopedResources) TestAccessListMemberScopedAndUnscoped() {
|
||||
require.True(s.T(),
|
||||
s.teleportFeatures.GetAdvancedAccessWorkflows(),
|
||||
"Test requires Advanced Access Workflows",
|
||||
)
|
||||
|
||||
ctx := s.T().Context()
|
||||
accessListClient := s.client.AccessListClient()
|
||||
|
||||
checkDestroyed := func(state *terraform.State) error {
|
||||
for _, tc := range []struct {
|
||||
accessListScope string
|
||||
accessListName string
|
||||
memberScope string
|
||||
memberName string
|
||||
}{
|
||||
{accessListName: "test-unscoped-member-list", memberName: "unscoped-fighter"},
|
||||
{accessListScope: "/foo/bar", accessListName: "test-scoped-member-list", memberName: "scoped-fighter"},
|
||||
{accessListScope: "/foo/bar", accessListName: "test-scoped-member-list", memberScope: "/foo/bar", memberName: "test-scoped-child-member-list"},
|
||||
} {
|
||||
_, err := accessListClient.GetStaticAccessListMemberV2(ctx, accesslistv1.GetStaticAccessListMemberRequest_builder{
|
||||
AccessListScope: tc.accessListScope,
|
||||
AccessList: tc.accessListName,
|
||||
MemberScope: tc.memberScope,
|
||||
MemberName: tc.memberName,
|
||||
}.Build())
|
||||
if err != nil && !trace.IsNotFound(err) {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
expires := time.Date(2038, 3, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
resource.Test(s.T(), resource.TestCase{
|
||||
ProtoV6ProviderFactories: s.terraformProviders,
|
||||
CheckDestroy: checkDestroyed,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: s.getFixture("access_list_member_scoped_and_unscoped.tf"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.unscoped", "header.metadata.name", "unscoped-fighter"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.unscoped", "spec.access_list", "test-unscoped-member-list"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.scoped", "header.metadata.name", "scoped-fighter"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.scoped", "spec.access_list", "/foo/bar::test-scoped-member-list"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.scoped", "scope", "/foo/bar"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.scoped_list", "header.metadata.name", "/foo/bar::test-scoped-child-member-list"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.scoped_list", "spec.access_list", "/foo/bar::test-scoped-member-list"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.scoped_list", "spec.membership_kind", "3"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list_member.scoped_list", "scope", "/foo/bar"),
|
||||
checkAccessListMemberExistsWithScopes(ctx, accessListClient, "", "test-unscoped-member-list", "", "unscoped-fighter", "", "test-unscoped-member-list", expires),
|
||||
checkAccessListMemberExistsWithScopes(ctx, accessListClient, "/foo/bar", "test-scoped-member-list", "", "scoped-fighter", "/foo/bar", "/foo/bar::test-scoped-member-list", expires),
|
||||
checkScopedListAccessListMemberExists(ctx, accessListClient, "/foo/bar", "test-scoped-member-list", "test-scoped-child-member-list", expires),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: s.getFixture("access_list_member_scoped_and_unscoped.tf"),
|
||||
PlanOnly: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -28,6 +28,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/api/client"
|
||||
"github.com/gravitational/teleport/api/client/accesslist"
|
||||
accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1"
|
||||
)
|
||||
|
||||
type nextAuditDateComparer struct {
|
||||
@@ -35,20 +37,29 @@ type nextAuditDateComparer struct {
|
||||
nextAuditDate time.Time
|
||||
}
|
||||
|
||||
func (c *nextAuditDateComparer) CaptureNextAuditDate(name string) resource.TestCheckFunc {
|
||||
func (c *nextAuditDateComparer) CaptureNextAuditDate(ctx context.Context, scope, name string) resource.TestCheckFunc {
|
||||
return func(state *terraform.State) error {
|
||||
al, err := c.client.AccessListClient().GetAccessList(context.TODO(), name)
|
||||
al, err := c.client.AccessListClient().GetAccessListV2(ctx, &accesslistv1.GetAccessListRequest{
|
||||
Name: name,
|
||||
Scope: scope,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if al.Spec.Audit.NextAuditDate.IsZero() {
|
||||
return trace.BadParameter("NextAuditDate must not be zero")
|
||||
}
|
||||
c.nextAuditDate = al.Spec.Audit.NextAuditDate
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nextAuditDateComparer) TestNextAuditDateUnchanged(name string) resource.TestCheckFunc {
|
||||
func (c *nextAuditDateComparer) TestNextAuditDateUnchanged(ctx context.Context, scope, name string) resource.TestCheckFunc {
|
||||
return func(state *terraform.State) error {
|
||||
al, err := c.client.AccessListClient().GetAccessList(context.TODO(), name)
|
||||
al, err := c.client.AccessListClient().GetAccessListV2(ctx, &accesslistv1.GetAccessListRequest{
|
||||
Name: name,
|
||||
Scope: scope,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -60,6 +71,22 @@ func (c *nextAuditDateComparer) TestNextAuditDateUnchanged(name string) resource
|
||||
}
|
||||
}
|
||||
|
||||
func checkAccessListExists(ctx context.Context, clt *accesslist.Client, scope, name string) resource.TestCheckFunc {
|
||||
return func(state *terraform.State) error {
|
||||
al, err := clt.GetAccessListV2(ctx, &accesslistv1.GetAccessListRequest{
|
||||
Name: name,
|
||||
Scope: scope,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if al.GetScope() != scope {
|
||||
return trace.CompareFailed("access list scope mismatch: got %q, want %q", al.GetScope(), scope)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TerraformSuiteEnterprise) TestAccessList() {
|
||||
require.True(s.T(),
|
||||
s.teleportFeatures.GetAdvancedAccessWorkflows(),
|
||||
@@ -67,7 +94,9 @@ func (s *TerraformSuiteEnterprise) TestAccessList() {
|
||||
)
|
||||
|
||||
checkAccessListDestroyed := func(state *terraform.State) error {
|
||||
_, err := s.client.AccessListClient().GetAccessList(context.TODO(), "test")
|
||||
_, err := s.client.AccessListClient().GetAccessListV2(s.T().Context(), &accesslistv1.GetAccessListRequest{
|
||||
Name: "test",
|
||||
})
|
||||
if trace.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
@@ -91,7 +120,7 @@ func (s *TerraformSuiteEnterprise) TestAccessList() {
|
||||
resource.TestCheckResourceAttr(name, "spec.membership_requires.roles.0", "minion"),
|
||||
resource.TestCheckResourceAttr(name, "spec.grants.roles.0", "crane-operator"),
|
||||
resource.TestCheckResourceAttr(name, "spec.audit.recurrence.frequency", "3"),
|
||||
auditDateChecker.CaptureNextAuditDate("test"),
|
||||
auditDateChecker.CaptureNextAuditDate(s.T().Context(), "", "test"),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -104,7 +133,7 @@ func (s *TerraformSuiteEnterprise) TestAccessList() {
|
||||
resource.TestCheckResourceAttr(name, "spec.grants.traits.0.key", "allowed-machines"),
|
||||
resource.TestCheckResourceAttr(name, "spec.grants.traits.0.values.0", "crane"),
|
||||
resource.TestCheckResourceAttr(name, "spec.grants.traits.0.values.1", "forklift"),
|
||||
auditDateChecker.TestNextAuditDateUnchanged("test"),
|
||||
auditDateChecker.TestNextAuditDateUnchanged(s.T().Context(), "", "test"),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -115,7 +144,7 @@ func (s *TerraformSuiteEnterprise) TestAccessList() {
|
||||
Config: s.getFixture("access_list_2_expiring.tf"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(name, "header.metadata.expires", "2038-01-01T00:00:00Z"),
|
||||
auditDateChecker.TestNextAuditDateUnchanged("test"),
|
||||
auditDateChecker.TestNextAuditDateUnchanged(s.T().Context(), "", "test"),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -125,3 +154,94 @@ func (s *TerraformSuiteEnterprise) TestAccessList() {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *TerraformSuiteEnterprise) TestAccessListPreservesNextAuditDate() {
|
||||
require.True(s.T(),
|
||||
s.teleportFeatures.GetAdvancedAccessWorkflows(),
|
||||
"Test requires Advanced Access Workflows",
|
||||
)
|
||||
|
||||
checkAccessListDestroyed := func(state *terraform.State) error {
|
||||
_, err := s.client.AccessListClient().GetAccessListV2(s.T().Context(), &accesslistv1.GetAccessListRequest{
|
||||
Name: "test-next-audit-date",
|
||||
})
|
||||
if trace.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
name := "teleport_access_list.test"
|
||||
auditDateChecker := nextAuditDateComparer{client: s.client}
|
||||
|
||||
resource.Test(s.T(), resource.TestCase{
|
||||
ProtoV6ProviderFactories: s.terraformProviders,
|
||||
CheckDestroy: checkAccessListDestroyed,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: s.getFixture("access_list_next_audit_date_0_create.tf"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(name, "header.metadata.name", "test-next-audit-date"),
|
||||
resource.TestCheckResourceAttr(name, "spec.description", "next audit date test"),
|
||||
auditDateChecker.CaptureNextAuditDate(s.T().Context(), "", "test-next-audit-date"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: s.getFixture("access_list_next_audit_date_1_attempt_update.tf"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr(name, "spec.description", "updated next audit date test"),
|
||||
auditDateChecker.TestNextAuditDateUnchanged(s.T().Context(), "", "test-next-audit-date"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *TerraformSuiteEnterpriseScopedResources) TestAccessListScopedAndUnscoped() {
|
||||
require.True(s.T(),
|
||||
s.teleportFeatures.GetAdvancedAccessWorkflows(),
|
||||
"Test requires Advanced Access Workflows",
|
||||
)
|
||||
|
||||
checkDestroyed := func(state *terraform.State) error {
|
||||
for _, tc := range []struct {
|
||||
scope string
|
||||
name string
|
||||
}{
|
||||
{name: "test-unscoped"},
|
||||
{scope: "/foo/bar", name: "test-scoped"},
|
||||
} {
|
||||
_, err := s.client.AccessListClient().GetAccessListV2(s.T().Context(), &accesslistv1.GetAccessListRequest{
|
||||
Name: tc.name,
|
||||
Scope: tc.scope,
|
||||
})
|
||||
if err != nil && !trace.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
resource.Test(s.T(), resource.TestCase{
|
||||
ProtoV6ProviderFactories: s.terraformProviders,
|
||||
CheckDestroy: checkDestroyed,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: s.getFixture("access_list_scoped_and_unscoped.tf"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("teleport_access_list.unscoped", "header.metadata.name", "test-unscoped"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list.unscoped", "spec.membership_requires.roles.0", "minion"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list.scoped", "header.metadata.name", "test-scoped"),
|
||||
resource.TestCheckResourceAttr("teleport_access_list.scoped", "scope", "/foo/bar"),
|
||||
checkAccessListExists(s.T().Context(), s.client.AccessListClient(), "", "test-unscoped"),
|
||||
checkAccessListExists(s.T().Context(), s.client.AccessListClient(), "/foo/bar", "test-scoped"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: s.getFixture("access_list_scoped_and_unscoped.tf"),
|
||||
PlanOnly: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
resource "teleport_access_list" "member_test" {
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-member-list"
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
type = "static"
|
||||
title = "Member test"
|
||||
description = "Access list member Terraform test"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
}
|
||||
]
|
||||
grants = {
|
||||
roles = ["crane-operator"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "teleport_access_list_member" "fighter" {
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "fighter"
|
||||
labels = {
|
||||
class = "fighter"
|
||||
}
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
access_list = teleport_access_list.member_test.id
|
||||
membership_kind = 1
|
||||
reason = "joined the party"
|
||||
expires = "2038-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
resource "teleport_access_list" "member_test" {
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-member-list"
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
type = "static"
|
||||
title = "Member test"
|
||||
description = "Access list member Terraform test"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
}
|
||||
]
|
||||
grants = {
|
||||
roles = ["crane-operator"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "teleport_access_list_member" "fighter" {
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "fighter"
|
||||
labels = {
|
||||
class = "veteran"
|
||||
}
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
access_list = teleport_access_list.member_test.id
|
||||
membership_kind = 1
|
||||
reason = "joined the party"
|
||||
expires = "2038-02-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
resource "teleport_access_list" "unscoped" {
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-unscoped-member-list"
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
type = "static"
|
||||
title = "Unscoped member list"
|
||||
description = "unscoped access list member test"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
}
|
||||
]
|
||||
grants = {
|
||||
roles = ["crane-operator"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "teleport_access_list" "scoped" {
|
||||
depends_on = [teleport_access_list.unscoped]
|
||||
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-scoped-member-list"
|
||||
}
|
||||
}
|
||||
scope = "/foo/bar"
|
||||
spec = {
|
||||
type = "static"
|
||||
title = "Scoped member list"
|
||||
description = "scoped access list member test"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "teleport_access_list" "scoped_child" {
|
||||
depends_on = [teleport_access_list.scoped]
|
||||
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-scoped-child-member-list"
|
||||
}
|
||||
}
|
||||
scope = "/foo/bar"
|
||||
spec = {
|
||||
type = "static"
|
||||
title = "Scoped child member list"
|
||||
description = "scoped child access list member test"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "teleport_access_list_member" "unscoped" {
|
||||
depends_on = [teleport_access_list.scoped_child]
|
||||
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "unscoped-fighter"
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
access_list = teleport_access_list.unscoped.id
|
||||
membership_kind = 1
|
||||
reason = "unscoped member"
|
||||
expires = "2038-03-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
|
||||
resource "teleport_access_list_member" "scoped" {
|
||||
depends_on = [teleport_access_list_member.unscoped]
|
||||
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "scoped-fighter"
|
||||
}
|
||||
}
|
||||
scope = "/foo/bar"
|
||||
spec = {
|
||||
access_list = teleport_access_list.scoped.id
|
||||
membership_kind = 1
|
||||
reason = "scoped member"
|
||||
expires = "2038-03-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
|
||||
resource "teleport_access_list_member" "scoped_list" {
|
||||
depends_on = [teleport_access_list_member.scoped]
|
||||
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = teleport_access_list.scoped_child.id
|
||||
}
|
||||
}
|
||||
scope = "/foo/bar"
|
||||
spec = {
|
||||
access_list = teleport_access_list.scoped.id
|
||||
membership_kind = 3
|
||||
reason = "scoped list member"
|
||||
expires = "2038-03-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
resource "teleport_access_list" "test" {
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-next-audit-date"
|
||||
labels = {
|
||||
example = "yes"
|
||||
}
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
description = "next audit date test"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
description = "The supervillain."
|
||||
}
|
||||
]
|
||||
membership_requires = {
|
||||
roles = ["minion"]
|
||||
}
|
||||
ownership_requires = {
|
||||
roles = ["supervillain"]
|
||||
}
|
||||
grants = {
|
||||
roles = ["crane-operator"]
|
||||
}
|
||||
title = "Next Audit Date"
|
||||
audit = {
|
||||
recurrence = {
|
||||
frequency = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
resource "teleport_access_list" "test" {
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-next-audit-date"
|
||||
labels = {
|
||||
example = "yes"
|
||||
}
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
description = "updated next audit date test"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
description = "The supervillain."
|
||||
}
|
||||
]
|
||||
membership_requires = {
|
||||
roles = ["minion"]
|
||||
}
|
||||
ownership_requires = {
|
||||
roles = ["supervillain"]
|
||||
}
|
||||
grants = {
|
||||
roles = ["crane-operator"]
|
||||
}
|
||||
title = "Next Audit Date"
|
||||
audit = {
|
||||
// Terraform should not clear or recompute the server-maintained next audit date during update.
|
||||
next_audit_date = null
|
||||
recurrence = {
|
||||
frequency = 3
|
||||
day_of_month = 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
resource "teleport_access_list" "unscoped" {
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-unscoped"
|
||||
}
|
||||
}
|
||||
spec = {
|
||||
description = "unscoped access list"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
description = "The supervillain."
|
||||
}
|
||||
]
|
||||
membership_requires = {
|
||||
roles = ["minion"]
|
||||
}
|
||||
ownership_requires = {
|
||||
roles = ["supervillain"]
|
||||
}
|
||||
grants = {
|
||||
roles = ["crane-operator"]
|
||||
}
|
||||
title = "Unscoped"
|
||||
audit = {
|
||||
recurrence = {
|
||||
frequency = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "teleport_access_list" "scoped" {
|
||||
depends_on = [teleport_access_list.unscoped]
|
||||
|
||||
header = {
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-scoped"
|
||||
}
|
||||
}
|
||||
scope = "/foo/bar"
|
||||
spec = {
|
||||
description = "scoped access list"
|
||||
owners = [
|
||||
{
|
||||
name = "gru"
|
||||
description = "The supervillain."
|
||||
}
|
||||
]
|
||||
title = "Scoped"
|
||||
audit = {
|
||||
recurrence = {
|
||||
frequency = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
data "teleport_classifier" "test" {
|
||||
kind = "classifier"
|
||||
version = "v1"
|
||||
metadata = {
|
||||
name = "test-classifier"
|
||||
|
||||
@@ -78,6 +78,9 @@ type TerraformSuiteOSSScopedResources struct {
|
||||
type TerraformSuiteEnterprise struct {
|
||||
TerraformBaseSuite
|
||||
}
|
||||
type TerraformSuiteEnterpriseScopedResources struct {
|
||||
TerraformBaseSuite
|
||||
}
|
||||
type TerraformSuiteEnterpriseWithCache struct {
|
||||
TerraformBaseSuite
|
||||
}
|
||||
|
||||
@@ -29,16 +29,19 @@ import (
|
||||
"github.com/gravitational/teleport/lib/modules"
|
||||
"github.com/gravitational/teleport/lib/modules/modulestest"
|
||||
"github.com/gravitational/teleport/lib/plugin"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
)
|
||||
|
||||
var testModules = &modulestest.Modules{
|
||||
TestFeatures: modules.Features{
|
||||
AdvancedAccessWorkflows: true,
|
||||
Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{
|
||||
entitlements.OIDC: {Enabled: true},
|
||||
entitlements.SAML: {Enabled: true},
|
||||
entitlements.DeviceTrust: {Enabled: true},
|
||||
entitlements.Policy: {Enabled: true},
|
||||
entitlements.OIDC: {Enabled: true},
|
||||
entitlements.SAML: {Enabled: true},
|
||||
entitlements.DeviceTrust: {Enabled: true},
|
||||
entitlements.Policy: {Enabled: true},
|
||||
entitlements.SessionSummaries: {Enabled: true},
|
||||
entitlements.AccessLists: {Enabled: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -82,6 +85,28 @@ func TestTerraformEnterprise(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTerraformEnterpriseScopedResources(t *testing.T) {
|
||||
authPlugin, err := NewPlugin(testModules)
|
||||
if trace.IsNotImplemented(err) {
|
||||
t.Skip(entTestSkipMessage)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
registry := plugin.NewRegistry()
|
||||
require.NoError(t, registry.Add(authPlugin))
|
||||
|
||||
suite.Run(t, &TerraformSuiteEnterpriseScopedResources{
|
||||
TerraformBaseSuite: TerraformBaseSuite{
|
||||
AuthHelper: &integration.MinimalAuthHelper{
|
||||
PluginRegistry: registry,
|
||||
AuthConfig: authtest.AuthServerConfig{
|
||||
Modules: testModules,
|
||||
ScopesFeatures: scopes.Features{Enabled: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
func TestTerraformEnterpriseCloud(t *testing.T) {
|
||||
authPlugin, err := NewPlugin(testModulesCloud)
|
||||
if trace.IsNotImplemented(err) {
|
||||
|
||||
@@ -88,13 +88,6 @@ func GenSchemaAccessList(ctx context.Context) (github_com_hashicorp_terraform_pl
|
||||
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 is resource metadata.",
|
||||
Optional: true,
|
||||
@@ -121,9 +114,10 @@ func GenSchemaAccessList(ctx context.Context) (github_com_hashicorp_terraform_pl
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
},
|
||||
"scope": {
|
||||
Description: "scope is the scope of the Access List.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
Description: "scope is the scope of the Access List.",
|
||||
Optional: true,
|
||||
PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()},
|
||||
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{
|
||||
@@ -390,13 +384,6 @@ func GenSchemaMember(ctx context.Context) (github_com_hashicorp_terraform_plugin
|
||||
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 is resource metadata.",
|
||||
Optional: true,
|
||||
@@ -423,9 +410,10 @@ func GenSchemaMember(ctx context.Context) (github_com_hashicorp_terraform_plugin
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
},
|
||||
"scope": {
|
||||
Description: "scope is the scope of the Access List member, it must be equal to the scope of the parent Access List.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
Description: "scope is the scope of the Access List member, it must be equal to the scope of the parent Access List.",
|
||||
Optional: true,
|
||||
PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()},
|
||||
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{
|
||||
@@ -644,23 +632,6 @@ func CopyAccessListFromTerraform(_ context.Context, tf github_com_hashicorp_terr
|
||||
}
|
||||
CopyFromTimestamp(diags, a, &obj.Expires)
|
||||
}
|
||||
{
|
||||
a, ok := tf.Attrs["revision"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"AccessList.header.metadata.revision"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"AccessList.header.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1789,28 +1760,6 @@ func CopyAccessListToTerraform(ctx context.Context, obj *github_com_gravitationa
|
||||
tf.Attrs["expires"] = v
|
||||
}
|
||||
}
|
||||
{
|
||||
t, ok := tf.AttrTypes["revision"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"AccessList.header.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{"AccessList.header.metadata.revision", err})
|
||||
}
|
||||
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"AccessList.header.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
|
||||
@@ -3471,23 +3420,6 @@ func CopyMemberFromTerraform(_ context.Context, tf github_com_hashicorp_terrafor
|
||||
}
|
||||
CopyFromTimestamp(diags, a, &obj.Expires)
|
||||
}
|
||||
{
|
||||
a, ok := tf.Attrs["revision"]
|
||||
if !ok {
|
||||
diags.Append(attrReadMissingDiag{"Member.header.metadata.revision"})
|
||||
} else {
|
||||
v, ok := a.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrReadConversionFailureDiag{"Member.header.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3884,28 +3816,6 @@ func CopyMemberToTerraform(ctx context.Context, obj *github_com_gravitational_te
|
||||
tf.Attrs["expires"] = v
|
||||
}
|
||||
}
|
||||
{
|
||||
t, ok := tf.AttrTypes["revision"]
|
||||
if !ok {
|
||||
diags.Append(attrWriteMissingDiag{"Member.header.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{"Member.header.metadata.revision", err})
|
||||
}
|
||||
v, ok = i.(github_com_hashicorp_terraform_plugin_framework_types.String)
|
||||
if !ok {
|
||||
diags.Append(attrWriteConversionFailureDiag{"Member.header.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
|
||||
|
||||
@@ -911,9 +911,10 @@ func GenSchemaAppV3(ctx context.Context) (github_com_hashicorp_terraform_plugin_
|
||||
Optional: true,
|
||||
},
|
||||
"scope": {
|
||||
Description: "The App's scope.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
Description: "The App's scope.",
|
||||
Optional: true,
|
||||
PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()},
|
||||
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{
|
||||
@@ -4467,9 +4468,10 @@ func GenSchemaKubernetesClusterV3(ctx context.Context) (github_com_hashicorp_ter
|
||||
Optional: true,
|
||||
},
|
||||
"scope": {
|
||||
Description: "The scope of the kube cluster.",
|
||||
Optional: true,
|
||||
Type: github_com_hashicorp_terraform_plugin_framework_types.StringType,
|
||||
Description: "The scope of the kube cluster.",
|
||||
Optional: true,
|
||||
PlanModifiers: []github_com_hashicorp_terraform_plugin_framework_tfsdk.AttributePlanModifier{github_com_hashicorp_terraform_plugin_framework_tfsdk.RequiresReplace()},
|
||||
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{
|
||||
|
||||
@@ -349,8 +349,10 @@ func NewAuthServer(cfg AuthServerConfig) (*AuthServer, error) {
|
||||
}
|
||||
|
||||
accessLists, err := local.NewAccessListServiceV2(local.AccessListServiceConfig{
|
||||
Backend: srv.Backend,
|
||||
Modules: cfg.Modules,
|
||||
Backend: srv.Backend,
|
||||
Modules: cfg.Modules,
|
||||
ScopesFeatures: cfg.ScopesFeatures,
|
||||
RunWhileLockedRetryInterval: cfg.RunWhileLockedRetryInterval,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
||||
Reference in New Issue
Block a user