Skip to content

Commit 7b65e2a

Browse files
committed
chore: migrate tfe_oauth_client data source to provider framework
1 parent cb52fb9 commit 7b65e2a

File tree

4 files changed

+221
-187
lines changed

4 files changed

+221
-187
lines changed
Lines changed: 218 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,163 +1,265 @@
11
// Copyright (c) HashiCorp, Inc.
22
// SPDX-License-Identifier: MPL-2.0
33

4-
// NOTE: This is a legacy resource and should be migrated to the Plugin
5-
// Framework if substantial modifications are planned. See
6-
// docs/new-resources.md if planning to use this code as boilerplate for
7-
// a new resource.
8-
94
package provider
105

116
import (
127
"context"
8+
"errors"
139
"fmt"
1410
"time"
1511

1612
"github.com/hashicorp/go-tfe"
17-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
18-
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
13+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
14+
"github.com/hashicorp/terraform-plugin-framework/datasource"
15+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
16+
"github.com/hashicorp/terraform-plugin-framework/diag"
17+
"github.com/hashicorp/terraform-plugin-framework/path"
18+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
19+
"github.com/hashicorp/terraform-plugin-framework/types"
20+
"github.com/hashicorp/terraform-plugin-log/tflog"
21+
)
22+
23+
var (
24+
// Compile-time proof of interface implementation.
25+
_ datasource.DataSource = &dataSourceTFEOAuthClient{}
26+
_ datasource.DataSourceWithConfigure = &dataSourceTFEOAuthClient{}
1927
)
2028

21-
func dataSourceTFEOAuthClient() *schema.Resource {
22-
return &schema.Resource{
23-
Read: dataSourceTFEOAuthClientRead,
24-
Schema: map[string]*schema.Schema{
25-
"oauth_client_id": {
26-
Type: schema.TypeString,
27-
Optional: true,
28-
AtLeastOneOf: []string{"oauth_client_id", "name", "service_provider"},
29+
func NewOAuthClientDataSource() datasource.DataSource {
30+
return &dataSourceTFEOAuthClient{}
31+
}
32+
33+
type dataSourceTFEOAuthClient struct {
34+
config ConfiguredClient
35+
}
36+
37+
type modelDataSourceTFEOAuthClient struct {
38+
ID types.String `tfsdk:"id"`
39+
Name types.String `tfsdk:"name"`
40+
Organization types.String `tfsdk:"organization"`
41+
OAuthClientID types.String `tfsdk:"oauth_client_id"`
42+
ServiceProvider types.String `tfsdk:"service_provider"`
43+
APIURL types.String `tfsdk:"api_url"`
44+
CallbackURL types.String `tfsdk:"callback_url"`
45+
CreatedAt types.String `tfsdk:"created_at"`
46+
HTTPURL types.String `tfsdk:"http_url"`
47+
OAuthTokenID types.String `tfsdk:"oauth_token_id"`
48+
ServiceProviderDisplayName types.String `tfsdk:"service_provider_display_name"`
49+
OrganizationScoped types.Bool `tfsdk:"organization_scoped"`
50+
ProjectIDs types.Set `tfsdk:"project_ids"`
51+
}
52+
53+
func modelDataSourceFromTFEOAuthClient(ctx context.Context, c *tfe.OAuthClient) (*modelDataSourceTFEOAuthClient, diag.Diagnostics) {
54+
var diags diag.Diagnostics
55+
m := modelDataSourceTFEOAuthClient{
56+
ID: types.StringValue(c.ID),
57+
Name: types.StringPointerValue(c.Name),
58+
Organization: types.StringValue(c.Organization.Name),
59+
OAuthClientID: types.StringValue(c.ID),
60+
ServiceProvider: types.StringValue(string(c.ServiceProvider)),
61+
APIURL: types.StringValue(c.APIURL),
62+
CallbackURL: types.StringValue(c.CallbackURL),
63+
CreatedAt: types.StringValue(c.CreatedAt.Format(time.RFC3339)),
64+
OrganizationScoped: types.BoolPointerValue(c.OrganizationScoped),
65+
HTTPURL: types.StringValue(c.HTTPURL),
66+
ServiceProviderDisplayName: types.StringValue(c.ServiceProviderName),
67+
}
68+
69+
// Set project IDs
70+
projectIDs := make([]string, len(c.Projects))
71+
for i, project := range c.Projects {
72+
projectIDs[i] = project.ID
73+
}
74+
75+
projectIDSet, diags := types.SetValueFrom(ctx, types.StringType, projectIDs)
76+
if diags.HasError() {
77+
return nil, diags
78+
}
79+
m.ProjectIDs = projectIDSet
80+
81+
// Set OAuth token ID
82+
switch len(c.OAuthTokens) {
83+
case 0:
84+
m.OAuthTokenID = types.StringValue("")
85+
case 1:
86+
m.OAuthTokenID = types.StringValue(c.OAuthTokens[0].ID)
87+
default:
88+
diags.AddError("Error parsing API result", fmt.Sprintf("unexpected number of OAuth tokens: %d", len(c.OAuthTokens)))
89+
}
90+
91+
return &m, diags
92+
}
93+
94+
// Configure implements datasource.ResourceWithConfigure
95+
func (d *dataSourceTFEOAuthClient) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
96+
// Early exit if provider is unconfigured (i.e. we're only validating config or something)
97+
if req.ProviderData == nil {
98+
return
99+
}
100+
client, ok := req.ProviderData.(ConfiguredClient)
101+
if !ok {
102+
resp.Diagnostics.AddError(
103+
"Unexpected resource Configure type",
104+
fmt.Sprintf("Expected tfe.ConfiguredClient, got %T. This is a bug in the tfe provider, so please report it on GitHub.", req.ProviderData),
105+
)
106+
}
107+
d.config = client
108+
}
109+
110+
// Metadata implements datasource.Resource
111+
func (d *dataSourceTFEOAuthClient) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
112+
resp.TypeName = req.ProviderTypeName + "_oauth_client"
113+
}
114+
115+
// Schema implements datasource.Resource
116+
func (d *dataSourceTFEOAuthClient) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
117+
resp.Schema = schema.Schema{
118+
Attributes: map[string]schema.Attribute{
119+
"id": schema.StringAttribute{
120+
Computed: true,
121+
Description: "Service-generated identifier for the variable",
29122
},
30-
"organization": {
31-
Type: schema.TypeString,
32-
Optional: true,
123+
124+
"organization": schema.StringAttribute{
125+
Description: "Name of the organization",
126+
Computed: true,
127+
Optional: true,
33128
},
34-
"name": {
35-
Type: schema.TypeString,
36-
Optional: true,
37-
RequiredWith: []string{"organization"},
129+
130+
"name": schema.StringAttribute{
131+
Description: "Display name for the OAuth Client",
132+
Optional: true,
133+
Validators: []validator.String{
134+
stringvalidator.AtLeastOneOf(
135+
path.MatchRoot("oauth_client_id"),
136+
path.MatchRoot("service_provider"),
137+
),
138+
},
38139
},
39-
"service_provider": {
40-
Type: schema.TypeString,
41-
Optional: true,
42-
RequiredWith: []string{"organization"},
43-
ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice(
44-
[]string{
45-
string(tfe.ServiceProviderAzureDevOpsServer),
46-
string(tfe.ServiceProviderAzureDevOpsServices),
47-
string(tfe.ServiceProviderBitbucket),
48-
string(tfe.ServiceProviderBitbucketDataCenter),
49-
string(tfe.ServiceProviderBitbucketServer),
50-
string(tfe.ServiceProviderBitbucketServerLegacy),
140+
141+
"oauth_client_id": schema.StringAttribute{
142+
Description: "OAuth Token ID for the OAuth Client",
143+
Optional: true,
144+
},
145+
146+
"service_provider": schema.StringAttribute{
147+
Description: "The VCS provider being connected with",
148+
Optional: true,
149+
Validators: []validator.String{
150+
stringvalidator.OneOf(
51151
string(tfe.ServiceProviderGithub),
52152
string(tfe.ServiceProviderGithubEE),
53153
string(tfe.ServiceProviderGitlab),
54154
string(tfe.ServiceProviderGitlabCE),
55155
string(tfe.ServiceProviderGitlabEE),
56-
},
57-
false,
58-
)),
156+
string(tfe.ServiceProviderBitbucket),
157+
string(tfe.ServiceProviderBitbucketServer),
158+
string(tfe.ServiceProviderBitbucketServerLegacy),
159+
string(tfe.ServiceProviderBitbucketDataCenter),
160+
string(tfe.ServiceProviderAzureDevOpsServer),
161+
string(tfe.ServiceProviderAzureDevOpsServices),
162+
),
163+
},
59164
},
60-
"service_provider_display_name": {
61-
Type: schema.TypeString,
62-
Computed: true,
165+
166+
"api_url": schema.StringAttribute{
167+
Description: "The base URL of the VCS provider's API",
168+
Computed: true,
63169
},
64-
"api_url": {
65-
Type: schema.TypeString,
66-
Computed: true,
170+
171+
"callback_url": schema.StringAttribute{
172+
Description: "The base URL of the VCS provider's API",
173+
Computed: true,
67174
},
68-
"callback_url": {
69-
Type: schema.TypeString,
70-
Computed: true,
175+
176+
"created_at": schema.StringAttribute{
177+
Description: "The base URL of the VCS provider's API",
178+
Computed: true,
71179
},
72-
"created_at": {
73-
Type: schema.TypeString,
74-
Computed: true,
180+
181+
"http_url": schema.StringAttribute{
182+
Description: "The homepage of the VCS provider",
183+
Computed: true,
75184
},
76-
"http_url": {
77-
Type: schema.TypeString,
78-
Computed: true,
185+
186+
"oauth_token_id": schema.StringAttribute{
187+
Description: "OAuth Token ID for the OAuth Client",
188+
Computed: true,
79189
},
80-
"oauth_token_id": {
81-
Type: schema.TypeString,
82-
Computed: true,
190+
191+
"service_provider_display_name": schema.StringAttribute{
192+
Description: "The display name of the VCS provider",
193+
Computed: true,
83194
},
84-
"organization_scoped": {
85-
Type: schema.TypeBool,
86-
Computed: true,
195+
196+
"organization_scoped": schema.BoolAttribute{
197+
Description: "Whether or not the oauth client is scoped to all projects and workspaces in the organization",
198+
Computed: true,
87199
},
88-
"project_ids": {
89-
Type: schema.TypeSet,
90-
Elem: &schema.Schema{Type: schema.TypeString},
91-
Computed: true,
200+
201+
"project_ids": schema.SetAttribute{
202+
Description: "The IDs of the projects that the OAuth client is associated with",
203+
Computed: true,
204+
ElementType: types.StringType,
92205
},
93206
},
94207
}
95208
}
96209

97-
func dataSourceTFEOAuthClientRead(d *schema.ResourceData, meta interface{}) error {
98-
ctx := context.TODO()
99-
config := meta.(ConfiguredClient)
210+
// Read implements datasource.Resource
211+
func (d *dataSourceTFEOAuthClient) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
212+
// Load the config into the model
213+
var config modelDataSourceTFEOAuthClient
214+
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
215+
if resp.Diagnostics.HasError() {
216+
return
217+
}
218+
219+
// Get the organization name from the data source or provider config
220+
var organization string
221+
resp.Diagnostics.Append(d.config.dataOrDefaultOrganization(ctx, req.Config, &organization)...)
222+
if resp.Diagnostics.HasError() {
223+
return
224+
}
225+
226+
id := config.OAuthClientID.ValueString()
227+
name := config.Name.ValueString()
228+
serviceProvider := tfe.ServiceProviderType(config.ServiceProvider.ValueString())
100229

101-
var oc *tfe.OAuthClient
102230
var err error
231+
var oc *tfe.OAuthClient
232+
tflog.Debug(ctx, fmt.Sprintf("Read OAuth client: %s", id))
103233

104-
switch v, ok := d.GetOk("oauth_client_id"); {
105-
case ok:
106-
oc, err = config.Client.OAuthClients.Read(ctx, v.(string))
107-
if err != nil {
108-
return fmt.Errorf("Error retrieving OAuth client: %w", err)
109-
}
110-
default:
111-
// search by name or service provider within a specific organization instead
112-
organization, err := config.schemaOrDefaultOrganization(d)
113-
if err != nil {
114-
return err
234+
if !config.OAuthClientID.IsNull() {
235+
// Read the OAuth client using its ID
236+
oc, err = d.config.Client.OAuthClients.Read(ctx, id)
237+
if err != nil && errors.Is(err, tfe.ErrResourceNotFound) {
238+
tflog.Debug(ctx, fmt.Sprintf("OAuth client %s no longer exists", id))
239+
resp.State.RemoveResource(ctx)
240+
return
115241
}
116242

117-
var name string
118-
var serviceProvider tfe.ServiceProviderType
119-
vName, ok := d.GetOk("name")
120-
if ok {
121-
name = vName.(string)
122-
}
123-
vServiceProvider, ok := d.GetOk("service_provider")
124-
if ok {
125-
serviceProvider = tfe.ServiceProviderType(vServiceProvider.(string))
243+
if err != nil {
244+
resp.Diagnostics.AddError("Error reading OAuth client", err.Error())
245+
return
126246
}
127-
128-
oc, err = fetchOAuthClientByNameOrServiceProvider(ctx, config.Client, organization, name, serviceProvider)
247+
} else {
248+
// Read the OAuth client using its name or service provider
249+
oc, err = fetchOAuthClientByNameOrServiceProvider(ctx, d.config.Client, organization, name, serviceProvider)
129250
if err != nil {
130-
return err
251+
resp.Diagnostics.AddError("Error reading OAuth client", err.Error())
252+
return
131253
}
132254
}
133255

134-
d.SetId(oc.ID)
135-
d.Set("oauth_client_id", oc.ID)
136-
d.Set("api_url", oc.APIURL)
137-
d.Set("callback_url", oc.CallbackURL)
138-
d.Set("created_at", oc.CreatedAt.Format(time.RFC3339))
139-
d.Set("http_url", oc.HTTPURL)
140-
if oc.Name != nil {
141-
d.Set("name", *oc.Name)
142-
}
143-
d.Set("service_provider", oc.ServiceProvider)
144-
d.Set("service_provider_display_name", oc.ServiceProviderName)
145-
d.Set("organization_scoped", oc.OrganizationScoped)
146-
147-
switch len(oc.OAuthTokens) {
148-
case 0:
149-
d.Set("oauth_token_id", "")
150-
case 1:
151-
d.Set("oauth_token_id", oc.OAuthTokens[0].ID)
152-
default:
153-
return fmt.Errorf("unexpected number of OAuth tokens: %d", len(oc.OAuthTokens))
154-
}
155-
156-
var projectIDs []interface{}
157-
for _, project := range oc.Projects {
158-
projectIDs = append(projectIDs, project.ID)
256+
// Load the result into the model
257+
result, diags := modelDataSourceFromTFEOAuthClient(ctx, oc)
258+
if diags.HasError() {
259+
resp.Diagnostics.Append(diags...)
260+
return
159261
}
160-
d.Set("project_ids", projectIDs)
161262

162-
return nil
263+
// Update state
264+
resp.Diagnostics.Append(resp.State.Set(ctx, result)...)
163265
}

0 commit comments

Comments
 (0)