Skip to content

TPT 2101: add users data source #895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 27, 2023
Merged
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ require (
golang.org/x/crypto v0.9.0
)

replace github.com/linode/linodego v1.17.0 => /Users/yechen/linode/linodego

require (
github.com/agext/levenshtein v1.2.2 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions linode/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/linode/terraform-provider-linode/linode/stackscripts"
"github.com/linode/terraform-provider-linode/linode/token"
"github.com/linode/terraform-provider-linode/linode/user"
"github.com/linode/terraform-provider-linode/linode/users"
"github.com/linode/terraform-provider-linode/linode/vlan"
"github.com/linode/terraform-provider-linode/linode/volume"
)
Expand Down Expand Up @@ -172,5 +173,6 @@ func (p *FrameworkProvider) DataSources(ctx context.Context) []func() datasource
databaseengines.NewDataSource,
region.NewDataSource,
vlan.NewDataSource,
users.NewDataSource,
}
}
58 changes: 58 additions & 0 deletions linode/helper/framework_datasource_base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package helper

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)

// NewBaseDataSource returns a new instance of the BaseDataSource
// struct for cleaner initialization.
func NewBaseDataSource(name string, schemaObject schema.Schema) BaseDataSource {
return BaseDataSource{
TypeName: name,
SchemaObject: schemaObject,
}
}

// BaseDataSource contains various re-usable fields and methods
// intended for use in data source implementations by composition.
type BaseDataSource struct {
Meta *FrameworkProviderMeta

SchemaObject schema.Schema
TypeName string
}

func (r *BaseDataSource) Configure(
ctx context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

r.Meta = GetDataSourceMeta(req, resp)
if resp.Diagnostics.HasError() {
return
}
}

func (r *BaseDataSource) Metadata(
ctx context.Context,
req datasource.MetadataRequest,
resp *datasource.MetadataResponse,
) {
resp.TypeName = r.TypeName
}

func (r *BaseDataSource) Schema(
ctx context.Context,
req datasource.SchemaRequest,
resp *datasource.SchemaResponse,
) {
resp.Schema = r.SchemaObject
}
3 changes: 3 additions & 0 deletions linode/user/datasource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ func TestAccDataSourceUser_basic(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceName, "username"),
resource.TestCheckResourceAttrSet(resourceName, "email"),
resource.TestCheckResourceAttrSet(resourceName, "tfa_enabled"),
resource.TestCheckResourceAttr(resourceName, "password_created", ""),
resource.TestCheckResourceAttr(resourceName, "verified_phone_number", ""),
),
},
{
Expand Down
4 changes: 2 additions & 2 deletions linode/user/framework_datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (d *DataSource) Read(
return
}

resp.Diagnostics.Append(data.parseUser(ctx, user)...)
resp.Diagnostics.Append(data.ParseUser(ctx, user)...)
if resp.Diagnostics.HasError() {
return
}
Expand All @@ -86,7 +86,7 @@ func (d *DataSource) Read(
)
return
}
resp.Diagnostics.Append(data.parseUserGrants(ctx, grants)...)
resp.Diagnostics.Append(data.ParseUserGrants(ctx, grants)...)
}
if resp.Diagnostics.HasError() {
return
Expand Down
99 changes: 58 additions & 41 deletions linode/user/framework_datasource_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,46 +38,63 @@ var linodeUserGrantsEntitySet = schema.SetAttribute{
ElementType: linodeUserGrantsEntityObjectType,
}

var frameworkDatasourceSchema = schema.Schema{
Attributes: map[string]schema.Attribute{
"username": schema.StringAttribute{
Description: "This User's username. This is used for logging in, and may also be displayed alongside " +
"actions the User performs (for example, in Events or public StackScripts).",
Required: true,
},
"ssh_keys": schema.ListAttribute{
Description: "A list of SSH Key labels added by this User. These are the keys that will be deployed " +
"if this User is included in the authorized_users field of a create Linode, rebuild Linode, or " +
"create Disk request.",
Computed: true,
ElementType: types.StringType,
},
"email": schema.StringAttribute{
Description: "The email address for this User, for account management communications, and may be used " +
"for other communications as configured.",
Computed: true,
},
"restricted": schema.BoolAttribute{
Description: "If true, this User must be granted access to perform actions or access entities on this Account.",
Computed: true,
},
"global_grants": schema.ListAttribute{
Description: "A structure containing the Account-level grants a User has.",
Computed: true,
ElementType: linodeUserGrantsGlobalObjectType,
},
"database_grant": linodeUserGrantsEntitySet,
"domain_grant": linodeUserGrantsEntitySet,
"firewall_grant": linodeUserGrantsEntitySet,
"image_grant": linodeUserGrantsEntitySet,
"linode_grant": linodeUserGrantsEntitySet,
"longview_grant": linodeUserGrantsEntitySet,
"nodebalancer_grant": linodeUserGrantsEntitySet,
"stackscript_grant": linodeUserGrantsEntitySet,
"volume_grant": linodeUserGrantsEntitySet,
"id": schema.StringAttribute{
Description: "Unique identifier for this DataSource.",
Computed: true,
},
var UserAttributes = map[string]schema.Attribute{
"username": schema.StringAttribute{
Description: "This User's username. This is used for logging in, and may also be displayed alongside " +
"actions the User performs (for example, in Events or public StackScripts).",
Required: true,
},
"ssh_keys": schema.ListAttribute{
Description: "A list of SSH Key labels added by this User. These are the keys that will be deployed " +
"if this User is included in the authorized_users field of a create Linode, rebuild Linode, or " +
"create Disk request.",
Computed: true,
ElementType: types.StringType,
},
"email": schema.StringAttribute{
Description: "The email address for this User, for account management communications, and may be used " +
"for other communications as configured.",
Computed: true,
},
"restricted": schema.BoolAttribute{
Description: "If true, this User must be granted access to perform actions or access entities on this Account.",
Computed: true,
},
"global_grants": schema.ListAttribute{
Description: "A structure containing the Account-level grants a User has.",
Computed: true,
ElementType: linodeUserGrantsGlobalObjectType,
},
"database_grant": linodeUserGrantsEntitySet,
"domain_grant": linodeUserGrantsEntitySet,
"firewall_grant": linodeUserGrantsEntitySet,
"image_grant": linodeUserGrantsEntitySet,
"linode_grant": linodeUserGrantsEntitySet,
"longview_grant": linodeUserGrantsEntitySet,
"nodebalancer_grant": linodeUserGrantsEntitySet,
"stackscript_grant": linodeUserGrantsEntitySet,
"volume_grant": linodeUserGrantsEntitySet,
"id": schema.StringAttribute{
Description: "Unique identifier for this DataSource.",
Computed: true,
},
"password_created": schema.StringAttribute{
Description: "The date and time when this User’s current password was created." +
"User passwords are first created during the Account sign-up process, and updated using the Reset Password webpage." +
"null if this User has not created a password yet.",
Computed: true,
},
"tfa_enabled": schema.BoolAttribute{
Description: "A boolean value indicating if the User has Two Factor Authentication (TFA) enabled.",
Computed: true,
},
"verified_phone_number": schema.StringAttribute{
Description: "The phone number verified for this User Profile with the Phone Number Verify command." +
"null if this User Profile has no verified phone number.",
Computed: true,
},
}

var frameworkDatasourceSchema = schema.Schema{
Attributes: UserAttributes,
}
53 changes: 36 additions & 17 deletions linode/user/framework_models.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,35 @@ import (
)

type DataSourceModel struct {
Username types.String `tfsdk:"username"`
SSHKeys types.List `tfsdk:"ssh_keys"`
Email types.String `tfsdk:"email"`
Restricted types.Bool `tfsdk:"restricted"`
GlobalGrants types.List `tfsdk:"global_grants"`
DomainGrant types.Set `tfsdk:"domain_grant"`
FirewallGrant types.Set `tfsdk:"firewall_grant"`
ImageGrant types.Set `tfsdk:"image_grant"`
LinodeGrant types.Set `tfsdk:"linode_grant"`
LongviewGrant types.Set `tfsdk:"longview_grant"`
NodebalancerGrant types.Set `tfsdk:"nodebalancer_grant"`
StackscriptGrant types.Set `tfsdk:"stackscript_grant"`
VolumeGrant types.Set `tfsdk:"volume_grant"`
DatabaseGrant types.Set `tfsdk:"database_grant"`
ID types.String `tfsdk:"id"`
Username types.String `tfsdk:"username"`
SSHKeys types.List `tfsdk:"ssh_keys"`
Email types.String `tfsdk:"email"`
Restricted types.Bool `tfsdk:"restricted"`
GlobalGrants types.List `tfsdk:"global_grants"`
DomainGrant types.Set `tfsdk:"domain_grant"`
FirewallGrant types.Set `tfsdk:"firewall_grant"`
ImageGrant types.Set `tfsdk:"image_grant"`
LinodeGrant types.Set `tfsdk:"linode_grant"`
LongviewGrant types.Set `tfsdk:"longview_grant"`
NodebalancerGrant types.Set `tfsdk:"nodebalancer_grant"`
StackscriptGrant types.Set `tfsdk:"stackscript_grant"`
VolumeGrant types.Set `tfsdk:"volume_grant"`
DatabaseGrant types.Set `tfsdk:"database_grant"`
ID types.String `tfsdk:"id"`
PasswordCreated types.String `tfsdk:"password_created"`
TFAEnabled types.Bool `tfsdk:"tfa_enabled"`
VerifiedPhoneNumber types.String `tfsdk:"verified_phone_number"`
}

func (data *DataSourceModel) parseUser(
func (data *DataSourceModel) ParseUser(
ctx context.Context, user *linodego.User,
) diag.Diagnostics {
data.Username = types.StringValue(user.Username)
data.Email = types.StringValue(user.Email)
data.Restricted = types.BoolValue(user.Restricted)
data.PasswordCreated = types.StringValue(user.PasswordCreated)
data.TFAEnabled = types.BoolValue(user.TFAEnabled)
data.VerifiedPhoneNumber = types.StringValue(user.VerifiedPhoneNumber)

sshKeys, diags := types.ListValueFrom(ctx, types.StringType, user.SSHKeys)
if diags.HasError() {
Expand All @@ -53,7 +59,7 @@ func (data *DataSourceModel) parseUser(
return nil
}

func (data *DataSourceModel) parseUserGrants(
func (data *DataSourceModel) ParseUserGrants(
ctx context.Context, userGrants *linodego.UserGrants,
) diag.Diagnostics {
// Domain
Expand Down Expand Up @@ -130,6 +136,19 @@ func (data *DataSourceModel) parseUserGrants(
return nil
}

func (data *DataSourceModel) ParseNonUserGrants() {
data.DatabaseGrant = types.SetNull(linodeUserGrantsEntityObjectType)
data.DomainGrant = types.SetNull(linodeUserGrantsEntityObjectType)
data.FirewallGrant = types.SetNull(linodeUserGrantsEntityObjectType)
data.GlobalGrants = types.ListNull(linodeUserGrantsGlobalObjectType)
data.ImageGrant = types.SetNull(linodeUserGrantsEntityObjectType)
data.LinodeGrant = types.SetNull(linodeUserGrantsEntityObjectType)
data.LongviewGrant = types.SetNull(linodeUserGrantsEntityObjectType)
data.NodebalancerGrant = types.SetNull(linodeUserGrantsEntityObjectType)
data.StackscriptGrant = types.SetNull(linodeUserGrantsEntityObjectType)
data.VolumeGrant = types.SetNull(linodeUserGrantsEntityObjectType)
}

func flattenGlobalGrants(ctx context.Context, grants linodego.GlobalUserGrants) (
*basetypes.ListValue, diag.Diagnostics,
) {
Expand Down
93 changes: 93 additions & 0 deletions linode/users/datasource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package users_test

import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/linode/terraform-provider-linode/linode/acceptance"
"github.com/linode/terraform-provider-linode/linode/users/tmpl"
)

func TestAccDataSourceUsers_basic(t *testing.T) {
t.Parallel()

resourceName := "data.linode_users.user"
username := acctest.RandomWithPrefix("tf-test")
email := username + "@example.com"

resource.Test(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: tmpl.DataBasic(t, username, email),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.username"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.email"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.tfa_enabled"),
resource.TestCheckResourceAttr(resourceName, "users.#", "1"),
resource.TestCheckResourceAttr(resourceName, "users.0.password_created", ""),
resource.TestCheckResourceAttr(resourceName, "users.0.verified_phone_number", ""),
resource.TestCheckResourceAttr(resourceName, "users.0.global_grants.#", "1"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.domain_grant.#"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.firewall_grant.#"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.image_grant.#"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.linode_grant.#"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.longview_grant.#"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.nodebalancer_grant.#"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.stackscript_grant.#"),
resource.TestCheckResourceAttrSet(resourceName, "users.0.volume_grant.#"),
),
},
},
})
}

func TestAccDataSourceUsers_clientFilter(t *testing.T) {
t.Parallel()

resourceName := "data.linode_users.user"
username := acctest.RandomWithPrefix("tf-test")
email := username + "@example.com"

resource.Test(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: tmpl.DataClientFilter(t, username, email),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttr(resourceName, "users.#", "1"),
resource.TestCheckResourceAttr(resourceName, "users.0.email", email),
resource.TestCheckResourceAttr(resourceName, "users.0.restricted", "true"),
),
},
},
})
}

func TestAccDataSourceUsers_substring(t *testing.T) {
t.Parallel()

resourceName := "data.linode_users.user"
username := acctest.RandomWithPrefix("tf-test")
email := username + "@example.com"

resource.Test(t, resource.TestCase{
PreCheck: func() { acceptance.PreCheck(t) },
ProtoV5ProviderFactories: acceptance.ProtoV5ProviderFactories,
Steps: []resource.TestStep{
{
Config: tmpl.DataSubstring(t, username, email),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttr(resourceName, "users.#", "2"),
acceptance.CheckResourceAttrContains(resourceName, "users.0.username", username),
),
},
},
})
}
Loading