diff --git a/docs/data-sources/local_user.md b/docs/data-sources/local_user.md index ec7b9a1..bfdc072 100644 --- a/docs/data-sources/local_user.md +++ b/docs/data-sources/local_user.md @@ -35,7 +35,7 @@ data "windows_local_user" "admin" { - `enabled` (Boolean) Get the status of the local user. - `full_name` (String) The full name of the local user. - `id` (String) The ID of the retrieved local user. This is the same as the SID. -- `last_login` (String) The last login time of the local user. +- `last_logon` (String) The last logon time of the local user. - `password_changeable_date` (String) The password changeable date of the local user. - `password_expires` (String) The time when the password of the local user expires. - `password_last_set` (String) The last time when the password was set for the local user. diff --git a/docs/data-sources/local_users.md b/docs/data-sources/local_users.md new file mode 100644 index 0000000..412d2e3 --- /dev/null +++ b/docs/data-sources/local_users.md @@ -0,0 +1,42 @@ +--- +page_title: "windows_local_users Data Source - terraform-provider-windows" +subcategory: "Local" +description: |- + Retrieve a list of all local users +--- +# windows_local_users (Data Source) + + +Retrieve a list of all local users. + + +## Schema + +### Read-Only + +- `users` (Attributes List) (see [below for nested schema](#nestedatt--users)) + + +### Nested Schema for `users` + +Required: + +- `name` (String) Define the name of the local user. + +Optional: + +- `sid` (String) The security ID of the local user. + +Read-Only: + +- `account_expires` (String) Retrieve the time where the local user account expires. +- `description` (String) The description of the local user. +- `enabled` (Boolean) Get the status of the local user. +- `full_name` (String) The full name of the local user. +- `id` (String) The ID of the retrieved local user. This is the same as the SID. +- `last_logon` (String) The last logon time of the local user. +- `password_changeable_date` (String) The password changeable date of the local user. +- `password_expires` (String) The time when the password of the local user expires. +- `password_last_set` (String) The last time when the password was set for the local user. +- `password_required` (Boolean) If true a password is required login with the local user. +- `user_may_change_password` (Boolean) If true the local user can change it's password. diff --git a/docs/resources/local_user.md b/docs/resources/local_user.md index ab23dac..93e94bf 100644 --- a/docs/resources/local_user.md +++ b/docs/resources/local_user.md @@ -26,7 +26,7 @@ resource "windows_local_user" "this" { ### Optional -- `account_expires` (String) Define when the local user account expires (UTC). If not specified, the user account never expires.
The string time format is the following: `yyyy-MM-dd hh:mm:ss` (see [go time package](https://pkg.go.dev/time#pkg-constants) `DateTime`). +- `account_expires` (String) Define when the local user account expires. If not specified, the user account never expires.
The string time format is the following: `2023-07-25T20:43:16Z` (see [Terraform timetypes](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes@v0.3.0/timetypes#RFC3339)). - `description` (String) Define a description for the local user. The maximum length is 48 characters. - `enabled` (Boolean) (Default: `true`)
Define whether the local user is enabled. - `full_name` (String) Define the full name of the local user. The full name differs from the user name of the user account. @@ -37,7 +37,7 @@ resource "windows_local_user" "this" { ### Read-Only - `id` (String) The ID of the retrieved local security group. This is the same as the SID. -- `last_login` (String) The last login time of the local user. +- `last_logon` (String) The last logon time of the local user. - `password_changeable_date` (String) The password changeable date of the local user. - `password_expires` (String) The time when the password of the local user expires. - `password_last_set` (String) The last time when the password was set for the local user. diff --git a/go.mod b/go.mod index 6e201e1..643e152 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/d-strobel/gowindows v0.0.0-20240105224217-566c4d2dcc50 github.com/hashicorp/terraform-plugin-docs v0.17.0 github.com/hashicorp/terraform-plugin-framework v1.5.0 + github.com/hashicorp/terraform-plugin-framework-timetypes v0.3.0 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 github.com/hashicorp/terraform-plugin-go v0.20.0 ) diff --git a/go.sum b/go.sum index 667e900..d3762bd 100644 --- a/go.sum +++ b/go.sum @@ -106,6 +106,8 @@ github.com/hashicorp/terraform-plugin-docs v0.17.0 h1:H1Yc+bgB//Geau5g7YKkhG5v9t github.com/hashicorp/terraform-plugin-docs v0.17.0/go.mod h1:cKC8GSLE+0a0bi7LtlpXgrqnlRDCGoGDn15PTEA+Ang= github.com/hashicorp/terraform-plugin-framework v1.5.0 h1:8kcvqJs/x6QyOFSdeAyEgsenVOUeC/IyKpi2ul4fjTg= github.com/hashicorp/terraform-plugin-framework v1.5.0/go.mod h1:6waavirukIlFpVpthbGd2PUNYaFedB0RwW3MDzJ/rtc= +github.com/hashicorp/terraform-plugin-framework-timetypes v0.3.0 h1:egR4InfakWkgepZNUATWGwkrPhaAYOTEybPfEol+G/I= +github.com/hashicorp/terraform-plugin-framework-timetypes v0.3.0/go.mod h1:9vjvl36aY1p6KltaA5QCvGC5hdE/9t4YuhGftw6WOgE= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= github.com/hashicorp/terraform-plugin-go v0.20.0 h1:oqvoUlL+2EUbKNsJbIt3zqqZ7wi6lzn4ufkn/UA51xQ= diff --git a/internal/generate/datasource_local_user/local_user_data_source_gen.go b/internal/generate/datasource_local_user/local_user_data_source_gen.go index c388175..a8d9028 100644 --- a/internal/generate/datasource_local_user/local_user_data_source_gen.go +++ b/internal/generate/datasource_local_user/local_user_data_source_gen.go @@ -4,6 +4,7 @@ package datasource_local_user import ( "context" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -13,6 +14,7 @@ func LocalUserDataSourceSchema(ctx context.Context) schema.Schema { return schema.Schema{ Attributes: map[string]schema.Attribute{ "account_expires": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, Description: "Retrieve the time where the local user account expires.", MarkdownDescription: "Retrieve the time where the local user account expires.", @@ -37,10 +39,11 @@ func LocalUserDataSourceSchema(ctx context.Context) schema.Schema { Description: "The ID of the retrieved local user. This is the same as the SID.", MarkdownDescription: "The ID of the retrieved local user. This is the same as the SID.", }, - "last_login": schema.StringAttribute{ + "last_logon": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, - Description: "The last login time of the local user.", - MarkdownDescription: "The last login time of the local user.", + Description: "The last logon time of the local user.", + MarkdownDescription: "The last logon time of the local user.", }, "name": schema.StringAttribute{ Required: true, @@ -48,16 +51,19 @@ func LocalUserDataSourceSchema(ctx context.Context) schema.Schema { MarkdownDescription: "Define the name of the local user.", }, "password_changeable_date": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, Description: "The password changeable date of the local user.", MarkdownDescription: "The password changeable date of the local user.", }, "password_expires": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, Description: "The time when the password of the local user expires.", MarkdownDescription: "The time when the password of the local user expires.", }, "password_last_set": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, Description: "The last time when the password was set for the local user.", MarkdownDescription: "The last time when the password was set for the local user.", @@ -82,17 +88,17 @@ func LocalUserDataSourceSchema(ctx context.Context) schema.Schema { } type LocalUserModel struct { - AccountExpires types.String `tfsdk:"account_expires"` - Description types.String `tfsdk:"description"` - Enabled types.Bool `tfsdk:"enabled"` - FullName types.String `tfsdk:"full_name"` - Id types.String `tfsdk:"id"` - LastLogin types.String `tfsdk:"last_login"` - Name types.String `tfsdk:"name"` - PasswordChangeableDate types.String `tfsdk:"password_changeable_date"` - PasswordExpires types.String `tfsdk:"password_expires"` - PasswordLastSet types.String `tfsdk:"password_last_set"` - PasswordRequired types.Bool `tfsdk:"password_required"` - Sid types.String `tfsdk:"sid"` - UserMayChangePassword types.Bool `tfsdk:"user_may_change_password"` + AccountExpires timetypes.RFC3339 `tfsdk:"account_expires"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + FullName types.String `tfsdk:"full_name"` + Id types.String `tfsdk:"id"` + LastLogon timetypes.RFC3339 `tfsdk:"last_logon"` + Name types.String `tfsdk:"name"` + PasswordChangeableDate timetypes.RFC3339 `tfsdk:"password_changeable_date"` + PasswordExpires timetypes.RFC3339 `tfsdk:"password_expires"` + PasswordLastSet timetypes.RFC3339 `tfsdk:"password_last_set"` + PasswordRequired types.Bool `tfsdk:"password_required"` + Sid types.String `tfsdk:"sid"` + UserMayChangePassword types.Bool `tfsdk:"user_may_change_password"` } diff --git a/internal/generate/datasource_local_users/local_users_data_source_gen.go b/internal/generate/datasource_local_users/local_users_data_source_gen.go new file mode 100644 index 0000000..639020a --- /dev/null +++ b/internal/generate/datasource_local_users/local_users_data_source_gen.go @@ -0,0 +1,1084 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package datasource_local_users + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +func LocalUsersDataSourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "users": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "account_expires": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + Description: "Retrieve the time where the local user account expires.", + MarkdownDescription: "Retrieve the time where the local user account expires.", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "The description of the local user.", + MarkdownDescription: "The description of the local user.", + }, + "enabled": schema.BoolAttribute{ + Computed: true, + Description: "Get the status of the local user.", + MarkdownDescription: "Get the status of the local user.", + }, + "full_name": schema.StringAttribute{ + Computed: true, + Description: "The full name of the local user.", + MarkdownDescription: "The full name of the local user.", + }, + "id": schema.StringAttribute{ + Computed: true, + Description: "The ID of the retrieved local user. This is the same as the SID.", + MarkdownDescription: "The ID of the retrieved local user. This is the same as the SID.", + }, + "last_logon": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + Description: "The last logon time of the local user.", + MarkdownDescription: "The last logon time of the local user.", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Define the name of the local user.", + MarkdownDescription: "Define the name of the local user.", + }, + "password_changeable_date": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + Description: "The password changeable date of the local user.", + MarkdownDescription: "The password changeable date of the local user.", + }, + "password_expires": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + Description: "The time when the password of the local user expires.", + MarkdownDescription: "The time when the password of the local user expires.", + }, + "password_last_set": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, + Computed: true, + Description: "The last time when the password was set for the local user.", + MarkdownDescription: "The last time when the password was set for the local user.", + }, + "password_required": schema.BoolAttribute{ + Computed: true, + Description: "If true a password is required login with the local user.", + MarkdownDescription: "If true a password is required login with the local user.", + }, + "sid": schema.StringAttribute{ + Optional: true, + Description: "The security ID of the local user.", + MarkdownDescription: "The security ID of the local user.", + }, + "user_may_change_password": schema.BoolAttribute{ + Computed: true, + Description: "If true the local user can change it's password.", + MarkdownDescription: "If true the local user can change it's password.", + }, + }, + CustomType: UsersType{ + ObjectType: types.ObjectType{ + AttrTypes: UsersValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + }, + }, + } +} + +type LocalUsersModel struct { + Users types.List `tfsdk:"users"` +} + +var _ basetypes.ObjectTypable = UsersType{} + +type UsersType struct { + basetypes.ObjectType +} + +func (t UsersType) Equal(o attr.Type) bool { + other, ok := o.(UsersType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t UsersType) String() string { + return "UsersType" +} + +func (t UsersType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + accountExpiresAttribute, ok := attributes["account_expires"] + + if !ok { + diags.AddError( + "Attribute Missing", + `account_expires is missing from object`) + + return nil, diags + } + + accountExpiresVal, ok := accountExpiresAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`account_expires expected to be basetypes.StringValue, was: %T`, accountExpiresAttribute)) + } + + descriptionAttribute, ok := attributes["description"] + + if !ok { + diags.AddError( + "Attribute Missing", + `description is missing from object`) + + return nil, diags + } + + descriptionVal, ok := descriptionAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute)) + } + + enabledAttribute, ok := attributes["enabled"] + + if !ok { + diags.AddError( + "Attribute Missing", + `enabled is missing from object`) + + return nil, diags + } + + enabledVal, ok := enabledAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`enabled expected to be basetypes.BoolValue, was: %T`, enabledAttribute)) + } + + fullNameAttribute, ok := attributes["full_name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `full_name is missing from object`) + + return nil, diags + } + + fullNameVal, ok := fullNameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`full_name expected to be basetypes.StringValue, was: %T`, fullNameAttribute)) + } + + idAttribute, ok := attributes["id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `id is missing from object`) + + return nil, diags + } + + idVal, ok := idAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`id expected to be basetypes.StringValue, was: %T`, idAttribute)) + } + + lastLogonAttribute, ok := attributes["last_logon"] + + if !ok { + diags.AddError( + "Attribute Missing", + `last_logon is missing from object`) + + return nil, diags + } + + lastLogonVal, ok := lastLogonAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`last_logon expected to be basetypes.StringValue, was: %T`, lastLogonAttribute)) + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return nil, diags + } + + nameVal, ok := nameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) + } + + passwordChangeableDateAttribute, ok := attributes["password_changeable_date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `password_changeable_date is missing from object`) + + return nil, diags + } + + passwordChangeableDateVal, ok := passwordChangeableDateAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`password_changeable_date expected to be basetypes.StringValue, was: %T`, passwordChangeableDateAttribute)) + } + + passwordExpiresAttribute, ok := attributes["password_expires"] + + if !ok { + diags.AddError( + "Attribute Missing", + `password_expires is missing from object`) + + return nil, diags + } + + passwordExpiresVal, ok := passwordExpiresAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`password_expires expected to be basetypes.StringValue, was: %T`, passwordExpiresAttribute)) + } + + passwordLastSetAttribute, ok := attributes["password_last_set"] + + if !ok { + diags.AddError( + "Attribute Missing", + `password_last_set is missing from object`) + + return nil, diags + } + + passwordLastSetVal, ok := passwordLastSetAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`password_last_set expected to be basetypes.StringValue, was: %T`, passwordLastSetAttribute)) + } + + passwordRequiredAttribute, ok := attributes["password_required"] + + if !ok { + diags.AddError( + "Attribute Missing", + `password_required is missing from object`) + + return nil, diags + } + + passwordRequiredVal, ok := passwordRequiredAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`password_required expected to be basetypes.BoolValue, was: %T`, passwordRequiredAttribute)) + } + + sidAttribute, ok := attributes["sid"] + + if !ok { + diags.AddError( + "Attribute Missing", + `sid is missing from object`) + + return nil, diags + } + + sidVal, ok := sidAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`sid expected to be basetypes.StringValue, was: %T`, sidAttribute)) + } + + userMayChangePasswordAttribute, ok := attributes["user_may_change_password"] + + if !ok { + diags.AddError( + "Attribute Missing", + `user_may_change_password is missing from object`) + + return nil, diags + } + + userMayChangePasswordVal, ok := userMayChangePasswordAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`user_may_change_password expected to be basetypes.BoolValue, was: %T`, userMayChangePasswordAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return UsersValue{ + AccountExpires: accountExpiresVal, + Description: descriptionVal, + Enabled: enabledVal, + FullName: fullNameVal, + Id: idVal, + LastLogon: lastLogonVal, + Name: nameVal, + PasswordChangeableDate: passwordChangeableDateVal, + PasswordExpires: passwordExpiresVal, + PasswordLastSet: passwordLastSetVal, + PasswordRequired: passwordRequiredVal, + Sid: sidVal, + UserMayChangePassword: userMayChangePasswordVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewUsersValueNull() UsersValue { + return UsersValue{ + state: attr.ValueStateNull, + } +} + +func NewUsersValueUnknown() UsersValue { + return UsersValue{ + state: attr.ValueStateUnknown, + } +} + +func NewUsersValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (UsersValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing UsersValue Attribute Value", + "While creating a UsersValue value, a missing attribute value was detected. "+ + "A UsersValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("UsersValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid UsersValue Attribute Type", + "While creating a UsersValue value, an invalid attribute value was detected. "+ + "A UsersValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("UsersValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("UsersValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra UsersValue Attribute Value", + "While creating a UsersValue value, an extra attribute value was detected. "+ + "A UsersValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra UsersValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewUsersValueUnknown(), diags + } + + accountExpiresAttribute, ok := attributes["account_expires"] + + if !ok { + diags.AddError( + "Attribute Missing", + `account_expires is missing from object`) + + return NewUsersValueUnknown(), diags + } + + accountExpiresVal, ok := accountExpiresAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`account_expires expected to be basetypes.StringValue, was: %T`, accountExpiresAttribute)) + } + + descriptionAttribute, ok := attributes["description"] + + if !ok { + diags.AddError( + "Attribute Missing", + `description is missing from object`) + + return NewUsersValueUnknown(), diags + } + + descriptionVal, ok := descriptionAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute)) + } + + enabledAttribute, ok := attributes["enabled"] + + if !ok { + diags.AddError( + "Attribute Missing", + `enabled is missing from object`) + + return NewUsersValueUnknown(), diags + } + + enabledVal, ok := enabledAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`enabled expected to be basetypes.BoolValue, was: %T`, enabledAttribute)) + } + + fullNameAttribute, ok := attributes["full_name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `full_name is missing from object`) + + return NewUsersValueUnknown(), diags + } + + fullNameVal, ok := fullNameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`full_name expected to be basetypes.StringValue, was: %T`, fullNameAttribute)) + } + + idAttribute, ok := attributes["id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `id is missing from object`) + + return NewUsersValueUnknown(), diags + } + + idVal, ok := idAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`id expected to be basetypes.StringValue, was: %T`, idAttribute)) + } + + lastLogonAttribute, ok := attributes["last_logon"] + + if !ok { + diags.AddError( + "Attribute Missing", + `last_logon is missing from object`) + + return NewUsersValueUnknown(), diags + } + + lastLogonVal, ok := lastLogonAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`last_logon expected to be basetypes.StringValue, was: %T`, lastLogonAttribute)) + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return NewUsersValueUnknown(), diags + } + + nameVal, ok := nameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) + } + + passwordChangeableDateAttribute, ok := attributes["password_changeable_date"] + + if !ok { + diags.AddError( + "Attribute Missing", + `password_changeable_date is missing from object`) + + return NewUsersValueUnknown(), diags + } + + passwordChangeableDateVal, ok := passwordChangeableDateAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`password_changeable_date expected to be basetypes.StringValue, was: %T`, passwordChangeableDateAttribute)) + } + + passwordExpiresAttribute, ok := attributes["password_expires"] + + if !ok { + diags.AddError( + "Attribute Missing", + `password_expires is missing from object`) + + return NewUsersValueUnknown(), diags + } + + passwordExpiresVal, ok := passwordExpiresAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`password_expires expected to be basetypes.StringValue, was: %T`, passwordExpiresAttribute)) + } + + passwordLastSetAttribute, ok := attributes["password_last_set"] + + if !ok { + diags.AddError( + "Attribute Missing", + `password_last_set is missing from object`) + + return NewUsersValueUnknown(), diags + } + + passwordLastSetVal, ok := passwordLastSetAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`password_last_set expected to be basetypes.StringValue, was: %T`, passwordLastSetAttribute)) + } + + passwordRequiredAttribute, ok := attributes["password_required"] + + if !ok { + diags.AddError( + "Attribute Missing", + `password_required is missing from object`) + + return NewUsersValueUnknown(), diags + } + + passwordRequiredVal, ok := passwordRequiredAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`password_required expected to be basetypes.BoolValue, was: %T`, passwordRequiredAttribute)) + } + + sidAttribute, ok := attributes["sid"] + + if !ok { + diags.AddError( + "Attribute Missing", + `sid is missing from object`) + + return NewUsersValueUnknown(), diags + } + + sidVal, ok := sidAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`sid expected to be basetypes.StringValue, was: %T`, sidAttribute)) + } + + userMayChangePasswordAttribute, ok := attributes["user_may_change_password"] + + if !ok { + diags.AddError( + "Attribute Missing", + `user_may_change_password is missing from object`) + + return NewUsersValueUnknown(), diags + } + + userMayChangePasswordVal, ok := userMayChangePasswordAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`user_may_change_password expected to be basetypes.BoolValue, was: %T`, userMayChangePasswordAttribute)) + } + + if diags.HasError() { + return NewUsersValueUnknown(), diags + } + + return UsersValue{ + AccountExpires: accountExpiresVal, + Description: descriptionVal, + Enabled: enabledVal, + FullName: fullNameVal, + Id: idVal, + LastLogon: lastLogonVal, + Name: nameVal, + PasswordChangeableDate: passwordChangeableDateVal, + PasswordExpires: passwordExpiresVal, + PasswordLastSet: passwordLastSetVal, + PasswordRequired: passwordRequiredVal, + Sid: sidVal, + UserMayChangePassword: userMayChangePasswordVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewUsersValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) UsersValue { + object, diags := NewUsersValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewUsersValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t UsersType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewUsersValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewUsersValueUnknown(), nil + } + + if in.IsNull() { + return NewUsersValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewUsersValueMust(UsersValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t UsersType) ValueType(ctx context.Context) attr.Value { + return UsersValue{} +} + +var _ basetypes.ObjectValuable = UsersValue{} + +type UsersValue struct { + AccountExpires basetypes.StringValue `tfsdk:"account_expires"` + Description basetypes.StringValue `tfsdk:"description"` + Enabled basetypes.BoolValue `tfsdk:"enabled"` + FullName basetypes.StringValue `tfsdk:"full_name"` + Id basetypes.StringValue `tfsdk:"id"` + LastLogon basetypes.StringValue `tfsdk:"last_logon"` + Name basetypes.StringValue `tfsdk:"name"` + PasswordChangeableDate basetypes.StringValue `tfsdk:"password_changeable_date"` + PasswordExpires basetypes.StringValue `tfsdk:"password_expires"` + PasswordLastSet basetypes.StringValue `tfsdk:"password_last_set"` + PasswordRequired basetypes.BoolValue `tfsdk:"password_required"` + Sid basetypes.StringValue `tfsdk:"sid"` + UserMayChangePassword basetypes.BoolValue `tfsdk:"user_may_change_password"` + state attr.ValueState +} + +func (v UsersValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 13) + + var val tftypes.Value + var err error + + attrTypes["account_expires"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["enabled"] = basetypes.BoolType{}.TerraformType(ctx) + attrTypes["full_name"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["id"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["last_logon"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["name"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["password_changeable_date"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["password_expires"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["password_last_set"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["password_required"] = basetypes.BoolType{}.TerraformType(ctx) + attrTypes["sid"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["user_may_change_password"] = basetypes.BoolType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 13) + + val, err = v.AccountExpires.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["account_expires"] = val + + val, err = v.Description.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["description"] = val + + val, err = v.Enabled.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["enabled"] = val + + val, err = v.FullName.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["full_name"] = val + + val, err = v.Id.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["id"] = val + + val, err = v.LastLogon.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["last_logon"] = val + + val, err = v.Name.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["name"] = val + + val, err = v.PasswordChangeableDate.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["password_changeable_date"] = val + + val, err = v.PasswordExpires.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["password_expires"] = val + + val, err = v.PasswordLastSet.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["password_last_set"] = val + + val, err = v.PasswordRequired.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["password_required"] = val + + val, err = v.Sid.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["sid"] = val + + val, err = v.UserMayChangePassword.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["user_may_change_password"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v UsersValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v UsersValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v UsersValue) String() string { + return "UsersValue" +} + +func (v UsersValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "account_expires": basetypes.StringType{}, + "description": basetypes.StringType{}, + "enabled": basetypes.BoolType{}, + "full_name": basetypes.StringType{}, + "id": basetypes.StringType{}, + "last_logon": basetypes.StringType{}, + "name": basetypes.StringType{}, + "password_changeable_date": basetypes.StringType{}, + "password_expires": basetypes.StringType{}, + "password_last_set": basetypes.StringType{}, + "password_required": basetypes.BoolType{}, + "sid": basetypes.StringType{}, + "user_may_change_password": basetypes.BoolType{}, + }, + map[string]attr.Value{ + "account_expires": v.AccountExpires, + "description": v.Description, + "enabled": v.Enabled, + "full_name": v.FullName, + "id": v.Id, + "last_logon": v.LastLogon, + "name": v.Name, + "password_changeable_date": v.PasswordChangeableDate, + "password_expires": v.PasswordExpires, + "password_last_set": v.PasswordLastSet, + "password_required": v.PasswordRequired, + "sid": v.Sid, + "user_may_change_password": v.UserMayChangePassword, + }) + + return objVal, diags +} + +func (v UsersValue) Equal(o attr.Value) bool { + other, ok := o.(UsersValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.AccountExpires.Equal(other.AccountExpires) { + return false + } + + if !v.Description.Equal(other.Description) { + return false + } + + if !v.Enabled.Equal(other.Enabled) { + return false + } + + if !v.FullName.Equal(other.FullName) { + return false + } + + if !v.Id.Equal(other.Id) { + return false + } + + if !v.LastLogon.Equal(other.LastLogon) { + return false + } + + if !v.Name.Equal(other.Name) { + return false + } + + if !v.PasswordChangeableDate.Equal(other.PasswordChangeableDate) { + return false + } + + if !v.PasswordExpires.Equal(other.PasswordExpires) { + return false + } + + if !v.PasswordLastSet.Equal(other.PasswordLastSet) { + return false + } + + if !v.PasswordRequired.Equal(other.PasswordRequired) { + return false + } + + if !v.Sid.Equal(other.Sid) { + return false + } + + if !v.UserMayChangePassword.Equal(other.UserMayChangePassword) { + return false + } + + return true +} + +func (v UsersValue) Type(ctx context.Context) attr.Type { + return UsersType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v UsersValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "account_expires": basetypes.StringType{}, + "description": basetypes.StringType{}, + "enabled": basetypes.BoolType{}, + "full_name": basetypes.StringType{}, + "id": basetypes.StringType{}, + "last_logon": basetypes.StringType{}, + "name": basetypes.StringType{}, + "password_changeable_date": basetypes.StringType{}, + "password_expires": basetypes.StringType{}, + "password_last_set": basetypes.StringType{}, + "password_required": basetypes.BoolType{}, + "sid": basetypes.StringType{}, + "user_may_change_password": basetypes.BoolType{}, + } +} diff --git a/internal/generate/resource_local_user/local_user_resource_gen.go b/internal/generate/resource_local_user/local_user_resource_gen.go index 5e9dd00..ca511a4 100644 --- a/internal/generate/resource_local_user/local_user_resource_gen.go +++ b/internal/generate/resource_local_user/local_user_resource_gen.go @@ -4,12 +4,15 @@ package resource_local_user import ( "context" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "regexp" "github.com/hashicorp/terraform-plugin-framework/resource/schema" ) @@ -18,25 +21,22 @@ func LocalUserResourceSchema(ctx context.Context) schema.Schema { return schema.Schema{ Attributes: map[string]schema.Attribute{ "account_expires": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Optional: true, Computed: true, - Description: "Define when the local user account expires (UTC). If not specified, the user account never expires.
The string time format is the following: `yyyy-MM-dd hh:mm:ss` (see [go time package](https://pkg.go.dev/time#pkg-constants) `DateTime`).", - MarkdownDescription: "Define when the local user account expires (UTC). If not specified, the user account never expires.
The string time format is the following: `yyyy-MM-dd hh:mm:ss` (see [go time package](https://pkg.go.dev/time#pkg-constants) `DateTime`).", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, + Description: "Define when the local user account expires. If not specified, the user account never expires.
The string time format is the following: `2023-07-25T20:43:16Z` (see [Terraform timetypes](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes@v0.3.0/timetypes#RFC3339)).", + MarkdownDescription: "Define when the local user account expires. If not specified, the user account never expires.
The string time format is the following: `2023-07-25T20:43:16Z` (see [Terraform timetypes](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes@v0.3.0/timetypes#RFC3339)).", + Default: stringdefault.StaticString("0001-01-01T00:00:00Z"), }, "description": schema.StringAttribute{ Optional: true, Computed: true, Description: "Define a description for the local user. The maximum length is 48 characters.", MarkdownDescription: "Define a description for the local user. The maximum length is 48 characters.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, Validators: []validator.String{ - stringvalidator.LengthBetween(1, 48), + stringvalidator.LengthAtMost(48), }, + Default: stringdefault.StaticString(""), }, "enabled": schema.BoolAttribute{ Optional: true, @@ -50,9 +50,7 @@ func LocalUserResourceSchema(ctx context.Context) schema.Schema { Computed: true, Description: "Define the full name of the local user. The full name differs from the user name of the user account.", MarkdownDescription: "Define the full name of the local user. The full name differs from the user name of the user account.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, + Default: stringdefault.StaticString(""), }, "id": schema.StringAttribute{ Computed: true, @@ -62,10 +60,11 @@ func LocalUserResourceSchema(ctx context.Context) schema.Schema { stringplanmodifier.UseStateForUnknown(), }, }, - "last_login": schema.StringAttribute{ + "last_logon": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, - Description: "The last login time of the local user.", - MarkdownDescription: "The last login time of the local user.", + Description: "The last logon time of the local user.", + MarkdownDescription: "The last logon time of the local user.", }, "name": schema.StringAttribute{ Required: true, @@ -76,6 +75,7 @@ func LocalUserResourceSchema(ctx context.Context) schema.Schema { }, Validators: []validator.String{ stringvalidator.LengthBetween(1, 20), + stringvalidator.RegexMatches(regexp.MustCompile(`^[^"/\[\]:;|=,+*?<>\@]+$`), `cannot contain the following characters: "/\[]:;|=,+*?<>@ `), }, }, "password": schema.StringAttribute{ @@ -88,16 +88,19 @@ func LocalUserResourceSchema(ctx context.Context) schema.Schema { }, }, "password_changeable_date": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, Description: "The password changeable date of the local user.", MarkdownDescription: "The password changeable date of the local user.", }, "password_expires": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, Description: "The time when the password of the local user expires.", MarkdownDescription: "The time when the password of the local user expires.", }, "password_last_set": schema.StringAttribute{ + CustomType: timetypes.RFC3339Type{}, Computed: true, Description: "The last time when the password was set for the local user.", MarkdownDescription: "The last time when the password was set for the local user.", @@ -134,19 +137,19 @@ func LocalUserResourceSchema(ctx context.Context) schema.Schema { } type LocalUserModel struct { - AccountExpires types.String `tfsdk:"account_expires"` - Description types.String `tfsdk:"description"` - Enabled types.Bool `tfsdk:"enabled"` - FullName types.String `tfsdk:"full_name"` - Id types.String `tfsdk:"id"` - LastLogin types.String `tfsdk:"last_login"` - Name types.String `tfsdk:"name"` - Password types.String `tfsdk:"password"` - PasswordChangeableDate types.String `tfsdk:"password_changeable_date"` - PasswordExpires types.String `tfsdk:"password_expires"` - PasswordLastSet types.String `tfsdk:"password_last_set"` - PasswordNeverExpires types.Bool `tfsdk:"password_never_expires"` - PasswordRequired types.Bool `tfsdk:"password_required"` - Sid types.String `tfsdk:"sid"` - UserMayChangePassword types.Bool `tfsdk:"user_may_change_password"` + AccountExpires timetypes.RFC3339 `tfsdk:"account_expires"` + Description types.String `tfsdk:"description"` + Enabled types.Bool `tfsdk:"enabled"` + FullName types.String `tfsdk:"full_name"` + Id types.String `tfsdk:"id"` + LastLogon timetypes.RFC3339 `tfsdk:"last_logon"` + Name types.String `tfsdk:"name"` + Password types.String `tfsdk:"password"` + PasswordChangeableDate timetypes.RFC3339 `tfsdk:"password_changeable_date"` + PasswordExpires timetypes.RFC3339 `tfsdk:"password_expires"` + PasswordLastSet timetypes.RFC3339 `tfsdk:"password_last_set"` + PasswordNeverExpires types.Bool `tfsdk:"password_never_expires"` + PasswordRequired types.Bool `tfsdk:"password_required"` + Sid types.String `tfsdk:"sid"` + UserMayChangePassword types.Bool `tfsdk:"user_may_change_password"` } diff --git a/internal/provider/local/local_user_data_source.go b/internal/provider/local/local_user_data_source.go index 09a842f..5a00426 100644 --- a/internal/provider/local/local_user_data_source.go +++ b/internal/provider/local/local_user_data_source.go @@ -8,7 +8,9 @@ import ( "github.com/d-strobel/gowindows" "github.com/d-strobel/gowindows/windows/local" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -50,6 +52,7 @@ func (d *localUserDataSource) Configure(ctx context.Context, req datasource.Conf func (d *localUserDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { var data datasource_local_user.LocalUserModel + var diag diag.Diagnostics // Read Terraform configuration data into the model resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) @@ -68,22 +71,33 @@ func (d *localUserDataSource) Read(ctx context.Context, req datasource.ReadReque if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read local user, got error: %s", err)) return - } // Set data - data.Id = types.StringValue(winResp.SID.Value) - data.Sid = types.StringValue(winResp.SID.Value) - data.Name = types.StringValue(winResp.Name) + data.AccountExpires, diag = timetypes.NewRFC3339Value(winResp.AccountExpires.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + data.Description = types.StringValue(winResp.Description) data.Enabled = types.BoolValue(winResp.Enabled) - data.PasswordRequired = types.BoolValue(winResp.PasswordRequired) - data.AccountExpires = types.StringValue(winResp.AccountExpires.Format(time.DateTime)) data.FullName = types.StringValue(winResp.FullName) - data.LastLogin = types.StringValue(winResp.LastLogon.Format(time.DateTime)) - data.PasswordChangeableDate = types.StringValue(winResp.PasswordChangeableDate.Format(time.DateTime)) - data.PasswordExpires = types.StringValue(winResp.PasswordExpires.Format(time.DateTime)) - data.PasswordLastSet = types.StringValue(winResp.PasswordLastSet.Format(time.DateTime)) + data.Id = types.StringValue(winResp.SID.Value) + + data.LastLogon, diag = timetypes.NewRFC3339Value(winResp.LastLogon.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.Name = types.StringValue(winResp.Name) + + data.PasswordChangeableDate, diag = timetypes.NewRFC3339Value(winResp.PasswordChangeableDate.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordExpires, diag = timetypes.NewRFC3339Value(winResp.PasswordExpires.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordLastSet, diag = timetypes.NewRFC3339Value(winResp.PasswordLastSet.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordRequired = types.BoolValue(winResp.PasswordRequired) + data.Sid = types.StringValue(winResp.SID.Value) data.UserMayChangePassword = types.BoolValue(winResp.UserMayChangePassword) // Save data into Terraform state diff --git a/internal/provider/local/local_user_resource.go b/internal/provider/local/local_user_resource.go index a1fbaed..cd72a65 100644 --- a/internal/provider/local/local_user_resource.go +++ b/internal/provider/local/local_user_resource.go @@ -8,6 +8,8 @@ import ( "github.com/d-strobel/gowindows" "github.com/d-strobel/gowindows/windows/local" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" @@ -55,22 +57,14 @@ func (r *localUserResource) Create(ctx context.Context, req resource.CreateReque // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + // Read time values from the plan + accountExpiresPlanValue, diag := data.AccountExpires.ValueRFC3339Time() + resp.Diagnostics.Append(diag...) + if resp.Diagnostics.HasError() { return } - // Convert time string to time - accountExpires := time.Time{} - if !data.AccountExpires.IsUnknown() { - var err error - - accountExpires, err = time.Parse(time.DateTime, data.AccountExpires.ValueString()) - if err != nil { - resp.Diagnostics.AddAttributeError(path.Root("account_expires"), "Config Error", fmt.Sprintf("Unable to parse time, got error: %s", err)) - return - } - } - // Create API call logic params := local.UserCreateParams{ Name: data.Name.ValueString(), @@ -80,7 +74,7 @@ func (r *localUserResource) Create(ctx context.Context, req resource.CreateReque Password: data.Password.ValueString(), PasswordNeverExpires: data.PasswordNeverExpires.ValueBool(), UserMayChangePassword: data.UserMayChangePassword.ValueBool(), - AccountExpires: accountExpires, + AccountExpires: accountExpiresPlanValue, } winResp, err := r.client.Local.UserCreate(ctx, params) @@ -90,16 +84,28 @@ func (r *localUserResource) Create(ctx context.Context, req resource.CreateReque } // Set data - data.AccountExpires = types.StringValue(winResp.AccountExpires.Format(time.DateTime)) + data.AccountExpires, diag = timetypes.NewRFC3339Value(winResp.AccountExpires.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + data.Description = types.StringValue(winResp.Description) data.Enabled = types.BoolValue(winResp.Enabled) data.FullName = types.StringValue(winResp.FullName) data.Id = types.StringValue(winResp.SID.Value) - data.LastLogin = types.StringValue(winResp.LastLogon.Format(time.DateTime)) + + data.LastLogon, diag = timetypes.NewRFC3339Value(winResp.LastLogon.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + data.Name = types.StringValue(winResp.Name) - data.PasswordChangeableDate = types.StringValue(winResp.PasswordChangeableDate.Format(time.DateTime)) - data.PasswordExpires = types.StringValue(winResp.PasswordChangeableDate.Format(time.DateTime)) - data.PasswordLastSet = types.StringValue(winResp.PasswordLastSet.Format(time.DateTime)) + + data.PasswordChangeableDate, diag = timetypes.NewRFC3339Value(winResp.PasswordChangeableDate.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordExpires, diag = timetypes.NewRFC3339Value(winResp.PasswordExpires.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordLastSet, diag = timetypes.NewRFC3339Value(winResp.PasswordLastSet.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + data.PasswordRequired = types.BoolValue(winResp.PasswordRequired) data.Sid = types.StringValue(winResp.SID.Value) data.UserMayChangePassword = types.BoolValue(winResp.UserMayChangePassword) @@ -110,6 +116,7 @@ func (r *localUserResource) Create(ctx context.Context, req resource.CreateReque func (r *localUserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data resource_local_user.LocalUserModel + var diag diag.Diagnostics // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) @@ -126,18 +133,30 @@ func (r *localUserResource) Read(ctx context.Context, req resource.ReadRequest, } // Set data - data.Id = types.StringValue(winResp.SID.Value) - data.Sid = types.StringValue(winResp.SID.Value) - data.Name = types.StringValue(winResp.Name) + data.AccountExpires, diag = timetypes.NewRFC3339Value(winResp.AccountExpires.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + data.Description = types.StringValue(winResp.Description) data.Enabled = types.BoolValue(winResp.Enabled) - data.PasswordRequired = types.BoolValue(winResp.PasswordRequired) - data.AccountExpires = types.StringValue(winResp.AccountExpires.Format(time.DateTime)) data.FullName = types.StringValue(winResp.FullName) - data.LastLogin = types.StringValue(winResp.LastLogon.Format(time.DateTime)) - data.PasswordChangeableDate = types.StringValue(winResp.PasswordChangeableDate.Format(time.DateTime)) - data.PasswordExpires = types.StringValue(winResp.PasswordExpires.Format(time.DateTime)) - data.PasswordLastSet = types.StringValue(winResp.PasswordLastSet.Format(time.DateTime)) + data.Id = types.StringValue(winResp.SID.Value) + + data.LastLogon, diag = timetypes.NewRFC3339Value(winResp.LastLogon.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.Name = types.StringValue(winResp.Name) + + data.PasswordChangeableDate, diag = timetypes.NewRFC3339Value(winResp.PasswordChangeableDate.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordExpires, diag = timetypes.NewRFC3339Value(winResp.PasswordExpires.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordLastSet, diag = timetypes.NewRFC3339Value(winResp.PasswordLastSet.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordRequired = types.BoolValue(winResp.PasswordRequired) + data.Sid = types.StringValue(winResp.SID.Value) data.UserMayChangePassword = types.BoolValue(winResp.UserMayChangePassword) // Save updated data into Terraform state @@ -150,32 +169,24 @@ func (r *localUserResource) Update(ctx context.Context, req resource.UpdateReque // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + // Read time values from the plan + accountExpiresValue, diag := data.AccountExpires.ValueRFC3339Time() + resp.Diagnostics.Append(diag...) + if resp.Diagnostics.HasError() { return } - // Convert time string to time - accountExpires := time.Time{} - if !data.AccountExpires.IsUnknown() { - var err error - - accountExpires, err = time.Parse(time.DateTime, data.AccountExpires.ValueString()) - if err != nil { - resp.Diagnostics.AddAttributeError(path.Root("account_expires"), "Config Error", fmt.Sprintf("Unable to parse time, got error: %s", err)) - return - } - } - // Update API call logic params := local.UserUpdateParams{ - SID: data.Sid.ValueString(), - AccountExpires: accountExpires, + AccountExpires: accountExpiresValue, Description: data.Description.ValueString(), Enabled: data.Enabled.ValueBool(), FullName: data.FullName.ValueString(), Password: data.Password.ValueString(), PasswordNeverExpires: data.PasswordNeverExpires.ValueBool(), UserMayChangePassword: data.UserMayChangePassword.ValueBool(), + SID: data.Sid.ValueString(), } if err := r.client.Local.UserUpdate(ctx, params); err != nil { @@ -190,18 +201,30 @@ func (r *localUserResource) Update(ctx context.Context, req resource.UpdateReque } // Set data - data.Id = types.StringValue(winResp.SID.Value) - data.Sid = types.StringValue(winResp.SID.Value) - data.Name = types.StringValue(winResp.Name) + data.AccountExpires, diag = timetypes.NewRFC3339Value(winResp.AccountExpires.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + data.Description = types.StringValue(winResp.Description) data.Enabled = types.BoolValue(winResp.Enabled) - data.PasswordRequired = types.BoolValue(winResp.PasswordRequired) - data.AccountExpires = types.StringValue(winResp.AccountExpires.Format(time.DateTime)) data.FullName = types.StringValue(winResp.FullName) - data.LastLogin = types.StringValue(winResp.LastLogon.Format(time.DateTime)) - data.PasswordChangeableDate = types.StringValue(winResp.PasswordChangeableDate.Format(time.DateTime)) - data.PasswordExpires = types.StringValue(winResp.PasswordExpires.Format(time.DateTime)) - data.PasswordLastSet = types.StringValue(winResp.PasswordLastSet.Format(time.DateTime)) + data.Id = types.StringValue(winResp.SID.Value) + + data.LastLogon, diag = timetypes.NewRFC3339Value(winResp.LastLogon.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.Name = types.StringValue(winResp.Name) + + data.PasswordChangeableDate, diag = timetypes.NewRFC3339Value(winResp.PasswordChangeableDate.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordExpires, diag = timetypes.NewRFC3339Value(winResp.PasswordExpires.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordLastSet, diag = timetypes.NewRFC3339Value(winResp.PasswordLastSet.Format(time.RFC3339)) + resp.Diagnostics.Append(diag...) + + data.PasswordRequired = types.BoolValue(winResp.PasswordRequired) + data.Sid = types.StringValue(winResp.SID.Value) data.UserMayChangePassword = types.BoolValue(winResp.UserMayChangePassword) // Save updated data into Terraform state diff --git a/internal/provider/local/local_users_data_source.go b/internal/provider/local/local_users_data_source.go new file mode 100644 index 0000000..8ba135e --- /dev/null +++ b/internal/provider/local/local_users_data_source.go @@ -0,0 +1,96 @@ +package local + +import ( + "context" + "fmt" + "terraform-provider-windows/internal/generate/datasource_local_users" + "time" + + "github.com/d-strobel/gowindows" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ datasource.DataSource = (*localUsersDataSource)(nil) + +func NewLocalUsersDataSource() datasource.DataSource { + return &localUsersDataSource{} +} + +type localUsersDataSource struct { + client *gowindows.Client +} + +func (d *localUsersDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_local_users" +} + +func (d *localUsersDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = datasource_local_users.LocalUsersDataSourceSchema(ctx) + resp.Schema.Description = `Retrieve a list of all local users.` +} + +func (d *localUsersDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*gowindows.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *gowindows.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = client +} + +func (d *localUsersDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data datasource_local_users.LocalUsersModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Read API call logic + winResp, err := d.client.Local.UserList(ctx) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read local users, got error: %s", err)) + return + } + + var usersValueList []datasource_local_users.UsersValue + + for _, user := range winResp { + usersValue := datasource_local_users.UsersValue{ + AccountExpires: types.StringValue(user.AccountExpires.Format(time.RFC3339)), + Description: types.StringValue(user.Description), + Enabled: types.BoolValue(user.Enabled), + FullName: types.StringValue(user.FullName), + Id: types.StringValue(user.SID.Value), + LastLogon: types.StringValue(user.LastLogon.Format(time.RFC3339)), + Name: types.StringValue(user.Name), + PasswordChangeableDate: types.StringValue(user.PasswordChangeableDate.Format(time.RFC3339)), + PasswordExpires: types.StringValue(user.PasswordExpires.Format(time.RFC3339)), + PasswordLastSet: types.StringValue(user.PasswordLastSet.Format(time.RFC3339)), + PasswordRequired: types.BoolValue(user.PasswordRequired), + Sid: types.StringValue(user.SID.Value), + UserMayChangePassword: types.BoolValue(user.UserMayChangePassword), + } + + objVal, _ := usersValue.ToObjectValue(ctx) + newUsersValue, _ := datasource_local_users.NewUsersValue(objVal.AttributeTypes(ctx), objVal.Attributes()) + + usersValueList = append(usersValueList, newUsersValue) + } + + data.Users, _ = types.ListValueFrom(ctx, datasource_local_users.UsersValue{}.Type(ctx), usersValueList) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2b14d72..2cbb642 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -263,6 +263,7 @@ func (p *WindowsProvider) DataSources(ctx context.Context) []func() datasource.D local.NewLocalGroupDataSource, local.NewLocalGroupsDataSource, local.NewLocalUserDataSource, + local.NewLocalUsersDataSource, } } diff --git a/internal/schema/local_datasources.json b/internal/schema/local_datasources.json index 9ef5ec4..e0027c3 100644 --- a/internal/schema/local_datasources.json +++ b/internal/schema/local_datasources.json @@ -9,31 +9,31 @@ "schema": { "attributes": [ { - "name": "name", + "name": "description", "string": { - "computed_optional_required": "optional", - "description": "Define the name of the local security group." + "computed_optional_required": "computed", + "description": "The description of the local security group." } }, { - "name": "sid", + "name": "id", "string": { - "computed_optional_required": "optional", - "description": "Define the security ID of the local security group." + "computed_optional_required": "computed", + "description": "The ID of the retrieved local security group. This is the same as the SID." } }, { - "name": "description", + "name": "name", "string": { - "computed_optional_required": "computed", - "description": "The description of the local security group." + "computed_optional_required": "optional", + "description": "Define the name of the local security group." } }, { - "name": "id", + "name": "sid", "string": { - "computed_optional_required": "computed", - "description": "The ID of the retrieved local security group. This is the same as the SID." + "computed_optional_required": "optional", + "description": "Define the security ID of the local security group." } } ] @@ -50,31 +50,31 @@ "nested_object": { "attributes": [ { - "name": "name", + "name": "description", "string": { "computed_optional_required": "computed", - "description": "The name of the local security group." + "description": "The description of the local security group." } }, { - "name": "sid", + "name": "id", "string": { "computed_optional_required": "computed", - "description": "The security ID of the local security group." + "description": "The ID of the retrieved local security group. This is the same as the SID." } }, { - "name": "description", + "name": "name", "string": { "computed_optional_required": "computed", - "description": "The description of the local security group." + "description": "The name of the local security group." } }, { - "name": "id", + "name": "sid", "string": { "computed_optional_required": "computed", - "description": "The ID of the retrieved local security group. This is the same as the SID." + "description": "The security ID of the local security group." } } ] @@ -92,7 +92,14 @@ "name": "account_expires", "string": { "computed_optional_required": "computed", - "description": "Retrieve the time where the local user account expires." + "description": "Retrieve the time where the local user account expires.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } } }, { @@ -117,52 +124,80 @@ } }, { - "name": "password_changeable_date", + "name": "id", "string": { "computed_optional_required": "computed", - "description": "The password changeable date of the local user." + "description": "The ID of the retrieved local user. This is the same as the SID." } }, { - "name": "password_expires", + "name": "last_logon", "string": { "computed_optional_required": "computed", - "description": "The time when the password of the local user expires." + "description": "The last logon time of the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } } }, { - "name": "user_may_change_password", - "bool": { - "computed_optional_required": "computed", - "description": "If true the local user can change it's password." + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Define the name of the local user." } }, { - "name": "password_required", - "bool": { + "name": "password_changeable_date", + "string": { "computed_optional_required": "computed", - "description": "If true a password is required login with the local user." + "description": "The password changeable date of the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } } }, { - "name": "password_last_set", + "name": "password_expires", "string": { "computed_optional_required": "computed", - "description": "The last time when the password was set for the local user." + "description": "The time when the password of the local user expires.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } } }, { - "name": "last_login", + "name": "password_last_set", "string": { "computed_optional_required": "computed", - "description": "The last login time of the local user." + "description": "The last time when the password was set for the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } } }, { - "name": "name", - "string": { - "computed_optional_required": "required", - "description": "Define the name of the local user." + "name": "password_required", + "bool": { + "computed_optional_required": "computed", + "description": "If true a password is required login with the local user." } }, { @@ -173,10 +208,153 @@ } }, { - "name": "id", - "string": { + "name": "user_may_change_password", + "bool": { "computed_optional_required": "computed", - "description": "The ID of the retrieved local user. This is the same as the SID." + "description": "If true the local user can change it's password." + } + } + ] + } + }, + { + "name": "local_users", + "schema": { + "attributes": [ + { + "name": "users", + "list_nested": { + "computed_optional_required": "computed", + "nested_object": { + "attributes": [ + { + "name": "account_expires", + "string": { + "computed_optional_required": "computed", + "description": "Retrieve the time where the local user account expires.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } + } + }, + { + "name": "description", + "string": { + "computed_optional_required": "computed", + "description": "The description of the local user." + } + }, + { + "name": "enabled", + "bool": { + "computed_optional_required": "computed", + "description": "Get the status of the local user." + } + }, + { + "name": "full_name", + "string": { + "computed_optional_required": "computed", + "description": "The full name of the local user." + } + }, + { + "name": "id", + "string": { + "computed_optional_required": "computed", + "description": "The ID of the retrieved local user. This is the same as the SID." + } + }, + { + "name": "last_logon", + "string": { + "computed_optional_required": "computed", + "description": "The last logon time of the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Define the name of the local user." + } + }, + { + "name": "password_changeable_date", + "string": { + "computed_optional_required": "computed", + "description": "The password changeable date of the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } + } + }, + { + "name": "password_expires", + "string": { + "computed_optional_required": "computed", + "description": "The time when the password of the local user expires.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } + } + }, + { + "name": "password_last_set", + "string": { + "computed_optional_required": "computed", + "description": "The last time when the password was set for the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } + } + }, + { + "name": "password_required", + "bool": { + "computed_optional_required": "computed", + "description": "If true a password is required login with the local user." + } + }, + { + "name": "sid", + "string": { + "computed_optional_required": "optional", + "description": "The security ID of the local user." + } + }, + { + "name": "user_may_change_password", + "bool": { + "computed_optional_required": "computed", + "description": "If true the local user can change it's password." + } + } + ] + } } } ] diff --git a/internal/schema/local_resources.json b/internal/schema/local_resources.json index 92c7d04..b937075 100644 --- a/internal/schema/local_resources.json +++ b/internal/schema/local_resources.json @@ -8,56 +8,6 @@ "name": "local_group", "schema": { "attributes": [ - { - "name": "name", - "string": { - "computed_optional_required": "required", - "description": "Define the name for the local security group. The maximum length is 256 characters.", - "validators": [ - { - "custom": { - "imports": [ - { - "path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - } - ], - "schema_definition": "stringvalidator.LengthBetween(1, 256)" - } - } - ], - "plan_modifiers": [ - { - "custom": { - "imports": [ - { - "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - } - ], - "schema_definition": "stringplanmodifier.RequiresReplace()" - } - } - ] - } - }, - { - "name": "sid", - "string": { - "computed_optional_required": "computed", - "description": "The security ID of the local security group.", - "plan_modifiers": [ - { - "custom": { - "imports": [ - { - "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - } - ], - "schema_definition": "stringplanmodifier.UseStateForUnknown()" - } - } - ] - } - }, { "name": "description", "string": { @@ -98,19 +48,12 @@ } ] } - } - ] - } - }, - { - "name": "local_user", - "schema": { - "attributes": [ + }, { "name": "name", "string": { "computed_optional_required": "required", - "description": "Define the name for the local user. A user name can contain up to 20 uppercase characters or lowercase characters. A user name can't contain the following characters: `\"`, `/`, `\\`, `[`, `]`, `:`, `;`, `|`, `=`, `,`, `+`, `*`, `?`, `<`, `>`, `@`", + "description": "Define the name for the local security group. The maximum length is 256 characters.", "validators": [ { "custom": { @@ -119,7 +62,7 @@ "path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" } ], - "schema_definition": "stringvalidator.LengthBetween(1, 20)" + "schema_definition": "stringvalidator.LengthBetween(1, 256)" } } ], @@ -141,7 +84,7 @@ "name": "sid", "string": { "computed_optional_required": "computed", - "description": "The security ID of the local user.", + "description": "The security ID of the local security group.", "plan_modifiers": [ { "custom": { @@ -155,13 +98,36 @@ } ] } + } + ] + } + }, + { + "name": "local_user", + "schema": { + "attributes": [ + { + "name": "account_expires", + "string": { + "computed_optional_required": "computed_optional", + "description": "Define when the local user account expires. If not specified, the user account never expires.
The string time format is the following: `2023-07-25T20:43:16Z` (see [Terraform timetypes](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-framework-timetypes@v0.3.0/timetypes#RFC3339)).", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + }, + "default": { + "static": "0001-01-01T00:00:00Z" + } + } }, { - "name": "password", + "name": "description", "string": { - "computed_optional_required": "optional", - "sensitive": true, - "description": "Define a password for the local user. A password can contain up to 127 characters.", + "computed_optional_required": "computed_optional", + "description": "Define a description for the local user. The maximum length is 48 characters.", "validators": [ { "custom": { @@ -170,10 +136,23 @@ "path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" } ], - "schema_definition": "stringvalidator.LengthBetween(1, 127)" + "schema_definition": "stringvalidator.LengthAtMost(48)" } } - ] + ], + "default": { + "static": "" + } + } + }, + { + "name": "enabled", + "bool": { + "computed_optional_required": "computed_optional", + "description": "(Default: `true`)
Define whether the local user is enabled.", + "default": { + "static": true + } } }, { @@ -181,6 +160,16 @@ "string": { "computed_optional_required": "computed_optional", "description": "Define the full name of the local user. The full name differs from the user name of the user account.", + "default": { + "static": "" + } + } + }, + { + "name": "id", + "string": { + "computed_optional_required": "computed", + "description": "The ID of the retrieved local security group. This is the same as the SID.", "plan_modifiers": [ { "custom": { @@ -196,41 +185,49 @@ } }, { - "name": "description", + "name": "last_logon", "string": { - "computed_optional_required": "computed_optional", - "description": "Define a description for the local user. The maximum length is 48 characters.", - "plan_modifiers": [ + "computed_optional_required": "computed", + "description": "The last logon time of the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Define the name for the local user. A user name can contain up to 20 uppercase characters or lowercase characters. A user name can't contain the following characters: `\"`, `/`, `\\`, `[`, `]`, `:`, `;`, `|`, `=`, `,`, `+`, `*`, `?`, `<`, `>`, `@`", + "validators": [ { "custom": { "imports": [ { - "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" } ], - "schema_definition": "stringplanmodifier.UseStateForUnknown()" + "schema_definition": "stringvalidator.LengthBetween(1, 20)" } - } - ], - "validators": [ + }, { "custom": { "imports": [ { "path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + }, + { + "path": "regexp" } ], - "schema_definition": "stringvalidator.LengthBetween(1, 48)" + "schema_definition": "stringvalidator.RegexMatches(regexp.MustCompile(`^[^\"\/\\[\\]:;|=,+*?<>\\@]+$`), `cannot contain the following characters: \"/\\[]:;|=,+*?<>@ `)" } } - ] - } - }, - { - "name": "id", - "string": { - "computed_optional_required": "computed", - "description": "The ID of the retrieved local security group. This is the same as the SID.", + ], "plan_modifiers": [ { "custom": { @@ -239,73 +236,82 @@ "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" } ], - "schema_definition": "stringplanmodifier.UseStateForUnknown()" + "schema_definition": "stringplanmodifier.RequiresReplace()" } } ] } }, { - "name": "account_expires", + "name": "password", "string": { - "computed_optional_required": "computed_optional", - "description": "Define when the local user account expires (UTC). If not specified, the user account never expires.
The string time format is the following: `yyyy-MM-dd hh:mm:ss` (see [go time package](https://pkg.go.dev/time#pkg-constants) `DateTime`).", - "plan_modifiers": [ + "computed_optional_required": "optional", + "sensitive": true, + "description": "Define a password for the local user. A password can contain up to 127 characters.", + "validators": [ { "custom": { "imports": [ { - "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" } ], - "schema_definition": "stringplanmodifier.UseStateForUnknown()" + "schema_definition": "stringvalidator.LengthBetween(1, 127)" } } ] } }, { - "name": "enabled", - "bool": { - "computed_optional_required": "computed_optional", - "description": "(Default: `true`)
Define whether the local user is enabled.", - "default": { - "static": true + "name": "password_changeable_date", + "string": { + "computed_optional_required": "computed", + "description": "The password changeable date of the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" } } }, { - "name": "password_never_expires", - "bool": { - "computed_optional_required": "computed_optional", - "description": "(Default: `true`)
Define whether the password of the local user.", - "default": { - "static": true + "name": "password_expires", + "string": { + "computed_optional_required": "computed", + "description": "The time when the password of the local user expires.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" } } }, { - "name": "user_may_change_password", + "name": "password_never_expires", "bool": { "computed_optional_required": "computed_optional", - "description": "(Default: `true`)
Define whether the local user can change it's own password.", + "description": "(Default: `true`)
Define whether the password of the local user.", "default": { "static": true } } }, { - "name": "password_changeable_date", - "string": { - "computed_optional_required": "computed", - "description": "The password changeable date of the local user." - } - }, - { - "name": "password_expires", + "name": "password_last_set", "string": { "computed_optional_required": "computed", - "description": "The time when the password of the local user expires." + "description": "The last time when the password was set for the local user.", + "custom_type": { + "import": { + "path": "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + }, + "type": "timetypes.RFC3339Type{}", + "value_type": "timetypes.RFC3339" + } } }, { @@ -316,17 +322,32 @@ } }, { - "name": "password_last_set", + "name": "sid", "string": { "computed_optional_required": "computed", - "description": "The last time when the password was set for the local user." + "description": "The security ID of the local user.", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.UseStateForUnknown()" + } + } + ] } }, { - "name": "last_login", - "string": { - "computed_optional_required": "computed", - "description": "The last login time of the local user." + "name": "user_may_change_password", + "bool": { + "computed_optional_required": "computed_optional", + "description": "(Default: `true`)
Define whether the local user can change it's own password.", + "default": { + "static": true + } } } ] diff --git a/internal/schema/provider_windows.json b/internal/schema/provider_windows.json index 623ea23..22da447 100644 --- a/internal/schema/provider_windows.json +++ b/internal/schema/provider_windows.json @@ -12,135 +12,135 @@ } }, { - "name": "winrm", + "name": "kerberos", "single_nested": { "optional_required": "optional", - "description": "Define the WinRM connection parameters. Exactly one of 'winrm' or 'ssh' must be set for the provider to connect to a Windows target system. Define an empty 'winrm' attribute if you wish to use the environment variables.", + "description": "Define the Kerberos connection parameters. Currently this can only be combined with a WinRM connection.", "attributes": [ { - "name": "username", + "name": "krb_config_file", "string": { "optional_required": "optional", - "description": "(Env: `WIN_WINRM_USERNAME`)
Define the username to connect with the target Windows system. Required if winrm is set." + "description": "(Env: `WIN_KRB_CONFIG_FILE`)
Define the path to the kerberos configuration file. Required if kerberos is set." } }, { - "name": "password", + "name": "realm", "string": { "optional_required": "optional", - "description": "(Env: `WIN_WINRM_PASSWORD`)
Define the password to connect with the target Windows system. Required if winrm is set.", - "sensitive": true + "description": "(Env: `WIN_KRB_REALM`)
Define the Kerberos realm. Required if kerberos is set." } - }, + } + ] + } + }, + { + "name": "ssh", + "single_nested": { + "optional_required": "optional", + "description": "Define the SSH connection parameters. Exactly one of 'winrm' or 'ssh' must be set for the provider to connect to a Windows target system. Define an empty 'ssh' attribute if you wish to use the environment variables.", + "attributes": [ { - "name": "use_tls", + "name": "insecure", "bool": { "optional_required": "optional", - "description": "(Env: `WIN_WINRM_USE_TLS`) (Default: `true`)
Define if TLS (https) should be used to connect with the target Windows system." + "description": "(Env: `WIN_SSH_INSECURE`) (Default: `false`)
Accept insecure SSH connections. This includes e.g. the acceptance of unknown or changed host keys." } }, { - "name": "port", - "int64": { + "name": "known_hosts_path", + "string": { "optional_required": "optional", - "description": "(Env: `WIN_WINRM_PORT`) (Default: `5986`)
Define the port to connect with the target Windows system." + "description": "(Env: `WIN_SSH_KNOWN_HOSTS_PATH`)
Define the path to the known hosts file to connect with the target Windows system." } }, { - "name": "insecure", - "bool": { + "name": "password", + "string": { "optional_required": "optional", - "description": "(Env: `WIN_WINRM_INSECURE`) (Default: `false`)
Accept insecure WinRM connection. This includes e.g. the acceptance of untrusted certificates." + "sensitive": true, + "description": "(Env: `WIN_SSH_PASSWORD`)
Define the password to connect with the target Windows system. Exactly one of 'password', 'private_key' or 'private_key_path' is required if ssh is set." } }, { - "name": "timeout", + "name": "port", "int64": { "optional_required": "optional", - "description": "(Env: `WIN_WINRM_TIMEOUT`) (Default: `0`)
Define the connection timeout in minutes for the target Windows system." + "description": "(Env: `WIN_SSH_PORT`) (Default: `22`)
Define the port to connect with the target Windows system." } - } - ] - } - }, - { - "name": "kerberos", - "single_nested": { - "optional_required": "optional", - "description": "Define the Kerberos connection parameters. Currently this can only be combined with a WinRM connection.", - "attributes": [ + }, { - "name": "realm", + "name": "private_key", "string": { "optional_required": "optional", - "description": "(Env: `WIN_KRB_REALM`)
Define the Kerberos realm. Required if kerberos is set." + "sensitive": true, + "description": "(Env: `WIN_SSH_PRIVATE_KEY`)
Define the private key to connect with the target Windows system. Exactly one of 'password', 'private_key' or 'private_key_path' is required if ssh is set." } }, { - "name": "krb_config_file", + "name": "private_key_path", "string": { "optional_required": "optional", - "description": "(Env: `WIN_KRB_CONFIG_FILE`)
Define the path to the kerberos configuration file. Required if kerberos is set." + "description": "(Env: `WIN_SSH_PRIVATE_KEY_PATH`)
Define the path to the private key file to connect with the target Windows system. Exactly one of 'password', 'private_key' or 'private_key_path' is required if ssh is set." + } + }, + { + "name": "username", + "string": { + "optional_required": "optional", + "description": "(Env: `WIN_SSH_USERNAME`)
Define the username to connect with the target Windows system. Required if ssh is set." } } ] } }, { - "name": "ssh", + "name": "winrm", "single_nested": { "optional_required": "optional", - "description": "Define the SSH connection parameters. Exactly one of 'winrm' or 'ssh' must be set for the provider to connect to a Windows target system. Define an empty 'ssh' attribute if you wish to use the environment variables.", + "description": "Define the WinRM connection parameters. Exactly one of 'winrm' or 'ssh' must be set for the provider to connect to a Windows target system. Define an empty 'winrm' attribute if you wish to use the environment variables.", "attributes": [ { - "name": "username", - "string": { + "name": "insecure", + "bool": { "optional_required": "optional", - "description": "(Env: `WIN_SSH_USERNAME`)
Define the username to connect with the target Windows system. Required if ssh is set." + "description": "(Env: `WIN_WINRM_INSECURE`) (Default: `false`)
Accept insecure WinRM connection. This includes e.g. the acceptance of untrusted certificates." } }, { "name": "password", "string": { "optional_required": "optional", - "sensitive": true, - "description": "(Env: `WIN_SSH_PASSWORD`)
Define the password to connect with the target Windows system. Exactly one of 'password', 'private_key' or 'private_key_path' is required if ssh is set." - } - }, - { - "name": "private_key", - "string": { - "optional_required": "optional", - "sensitive": true, - "description": "(Env: `WIN_SSH_PRIVATE_KEY`)
Define the private key to connect with the target Windows system. Exactly one of 'password', 'private_key' or 'private_key_path' is required if ssh is set." + "description": "(Env: `WIN_WINRM_PASSWORD`)
Define the password to connect with the target Windows system. Required if winrm is set.", + "sensitive": true } }, { - "name": "private_key_path", - "string": { + "name": "port", + "int64": { "optional_required": "optional", - "description": "(Env: `WIN_SSH_PRIVATE_KEY_PATH`)
Define the path to the private key file to connect with the target Windows system. Exactly one of 'password', 'private_key' or 'private_key_path' is required if ssh is set." + "description": "(Env: `WIN_WINRM_PORT`) (Default: `5986`)
Define the port to connect with the target Windows system." } }, { - "name": "known_hosts_path", - "string": { + "name": "timeout", + "int64": { "optional_required": "optional", - "description": "(Env: `WIN_SSH_KNOWN_HOSTS_PATH`)
Define the path to the known hosts file to connect with the target Windows system." + "description": "(Env: `WIN_WINRM_TIMEOUT`) (Default: `0`)
Define the connection timeout in minutes for the target Windows system." } }, { - "name": "insecure", + "name": "use_tls", "bool": { "optional_required": "optional", - "description": "(Env: `WIN_SSH_INSECURE`) (Default: `false`)
Accept insecure SSH connections. This includes e.g. the acceptance of unknown or changed host keys." + "description": "(Env: `WIN_WINRM_USE_TLS`) (Default: `true`)
Define if TLS (https) should be used to connect with the target Windows system." } }, { - "name": "port", - "int64": { + "name": "username", + "string": { "optional_required": "optional", - "description": "(Env: `WIN_SSH_PORT`) (Default: `22`)
Define the port to connect with the target Windows system." + "description": "(Env: `WIN_WINRM_USERNAME`)
Define the username to connect with the target Windows system. Required if winrm is set." } } ]