-
Notifications
You must be signed in to change notification settings - Fork 16
/
provider.go
261 lines (233 loc) · 9.56 KB
/
provider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package provider
import (
"context"
"fmt"
"net/url"
"os"
"strings"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/prefecthq/terraform-provider-prefect/internal/client"
"github.com/prefecthq/terraform-provider-prefect/internal/provider/customtypes"
"github.com/prefecthq/terraform-provider-prefect/internal/provider/datasources"
"github.com/prefecthq/terraform-provider-prefect/internal/provider/helpers"
"github.com/prefecthq/terraform-provider-prefect/internal/provider/resources"
)
var _ = provider.Provider(&PrefectProvider{})
// New returns a new Prefect Provider instance.
//
//nolint:ireturn // required by Terraform API
func New() provider.Provider {
return &PrefectProvider{}
}
// Metadata returns the provider type name.
func (p *PrefectProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "prefect"
}
// Schema defines the provider-level schema for configuration data.
func (p *PrefectProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"endpoint": schema.StringAttribute{
Description: "Prefect API URL. Can also be set via the `PREFECT_API_URL` environment variable. Defaults to `https://api.prefect.cloud`",
Optional: true,
},
"api_key": schema.StringAttribute{
Description: "Prefect Cloud API Key. Can also be set via the `PREFECT_API_KEY` environment variable.",
Optional: true,
Sensitive: true,
},
"account_id": schema.StringAttribute{
CustomType: customtypes.UUIDType{},
Description: "Default Prefect Cloud Account ID. Can also be set via the `PREFECT_CLOUD_ACCOUNT_ID` environment variable.",
Optional: true,
},
"workspace_id": schema.StringAttribute{
CustomType: customtypes.UUIDType{},
Description: "Default Prefect Cloud Workspace ID.",
Optional: true,
},
},
}
}
// Configure configures the provider's internal client.
func (p *PrefectProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
config := &PrefectProviderModel{}
// Populate the model from provider configuration and emit diagnostics on error
resp.Diagnostics.Append(req.Config.Get(ctx, config)...)
if resp.Diagnostics.HasError() {
return
}
// Ensure that all configuration values passed in to provider are known
// https://developer.hashicorp.com/terraform/plugin/framework/handling-data/terraform-concepts#unknown-values
if config.Endpoint.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("endpoint"),
"Unknown Prefect API Endpoint",
"The Prefect API Endpoint is not known at configuration time. "+
"Potential resolutions: target apply the source of the value first, set the value statically in the configuration, or set the PREFECT_API_URL environment variable.",
)
}
if config.APIKey.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("api_key"),
"Unknown Prefect API Key",
"The Prefect API Key is not known at configuration time. "+
"Potential resolutions: target apply the source of the value first, set the value statically in the configuration, set the PREFECT_API_KEY environment variable, or remove the value.",
)
}
if config.AccountID.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("account_id"),
"Unknown Prefect Account ID",
"The Prefect Account ID is not known at configuration time. "+
"Potential resolutions: target apply the source of the value first, set the value statically in the configuration, or remove the value.",
)
}
if config.WorkspaceID.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("workspace_id"),
"Unknown Prefect Workspace ID",
"The Prefect Workspace ID is not known at configuration time. "+
"Potential resolutions: target apply the source of the value first, set the value statically in the configuration, or remove the value.",
)
}
if resp.Diagnostics.HasError() {
return
}
// Extract endpoint from configuration or environment variable.
// If the endpoint is not set, or the value is not a valid URL, emit an error.
var endpoint string
if !config.Endpoint.IsNull() {
endpoint = config.Endpoint.ValueString()
} else if apiURLEnvVar, ok := os.LookupEnv("PREFECT_API_URL"); ok {
endpoint = apiURLEnvVar
}
if endpoint == "" {
endpoint = "https://api.prefect.cloud"
}
// Here, we'll ensure that the /api suffix is present on the endpoint
if !strings.HasSuffix(endpoint, "/api") {
endpoint = fmt.Sprintf("%s/api", endpoint)
}
endpointURL, err := url.Parse(endpoint)
if err != nil {
resp.Diagnostics.AddAttributeError(
path.Root("endpoint"),
"Invalid Prefect API Endpoint",
fmt.Sprintf("The Prefect API Endpoint %q is not a valid URL: %s", endpoint, err),
)
}
isPrefectCloudEndpoint := helpers.IsCloudEndpoint(endpointURL.Host)
// Extract the API Key from configuration or environment variable.
var apiKey string
if !config.APIKey.IsNull() {
apiKey = config.APIKey.ValueString()
} else if apiKeyEnvVar, ok := os.LookupEnv("PREFECT_API_KEY"); ok {
apiKey = apiKeyEnvVar
}
// Extract the Account ID from configuration or environment variable.
// If the ID is set to an invalid UUID, emit an error.
var accountID uuid.UUID
if !config.AccountID.IsNull() {
accountID = config.AccountID.ValueUUID()
} else if accountIDEnvVar, ok := os.LookupEnv("PREFECT_CLOUD_ACCOUNT_ID"); ok {
accountID, err = uuid.Parse(accountIDEnvVar)
if err != nil {
resp.Diagnostics.AddAttributeWarning(
path.Root("account_id"),
"Invalid Prefect Account ID defined in PREFECT_CLOUD_ACCOUNT_ID ",
fmt.Sprintf("The PREFECT_CLOUD_ACCOUNT_ID value %q is not a valid UUID: %s", accountIDEnvVar, err),
)
}
}
// If the endpoint is pointed to Prefect Cloud, we will ensure
// that a valid API Key is passed.
// Additionally, we will warn if an Account ID is missing,
// as it's likely that this is a user misconfiguration.
if isPrefectCloudEndpoint {
if apiKey == "" {
resp.Diagnostics.AddAttributeError(
path.Root("api_key"),
"Missing Prefect API Key",
"The Prefect API Endpoint is configured to Prefect Cloud, however, the Prefect API Key is empty. "+
"Potential resolutions: set the endpoint attribute or PREFECT_API_URL environment variable to a Prefect server installation, set the PREFECT_API_KEY environment variable, or configure the api_key attribute.",
)
}
if accountID == uuid.Nil {
resp.Diagnostics.AddAttributeWarning(
path.Root("account_id"),
"Missing Prefect Account ID",
"The Prefect API Endpoint is configured to Prefect Cloud, however, the Prefect Account ID is empty. "+
"Potential resolutions: set the PREFECT_CLOUD_ACCOUNT_ID environment variable, or configure the account_id attribute.",
)
}
}
if resp.Diagnostics.HasError() {
return
}
ctx = tflog.SetField(ctx, "prefect_endpoint", endpoint)
ctx = tflog.SetField(ctx, "prefect_api_key", apiKey)
ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "prefect_api_key")
ctx = tflog.SetField(ctx, "prefect_account_id", accountID)
ctx = tflog.SetField(ctx, "prefect_workspace_id", config.WorkspaceID.ValueString())
tflog.Debug(ctx, "Creating Prefect client")
prefectClient, err := client.New(
client.WithEndpoint(endpoint),
client.WithAPIKey(apiKey),
client.WithDefaults(accountID, config.WorkspaceID.ValueUUID()),
)
if err != nil {
resp.Diagnostics.AddError(
"Unable to create Prefect API Client",
fmt.Sprintf("An unexpected error occurred when creating the Prefect API client. This is a bug in the provider, please create an issue against https://github.com/PrefectHQ/terraform-provider-prefect unless it has already been reported. "+
"Error returned by the client: %s", err),
)
return
}
p.client = prefectClient
// Pass client to DataSource and Resource type Configure methods
resp.DataSourceData = prefectClient
resp.ResourceData = prefectClient
tflog.Info(ctx, "Configured Prefect client", map[string]any{"success": true})
}
// DataSources defines the data sources implemented in the provider.
func (p *PrefectProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
datasources.NewAccountDataSource,
datasources.NewAccountMemberDataSource,
datasources.NewAccountMembersDataSource,
datasources.NewAccountRoleDataSource,
datasources.NewBlockDataSource,
datasources.NewServiceAccountDataSource,
datasources.NewTeamDataSource,
datasources.NewTeamsDataSource,
datasources.NewVariableDataSource,
datasources.NewWorkerMetadataDataSource,
datasources.NewWorkPoolDataSource,
datasources.NewWorkPoolsDataSource,
datasources.NewWorkspaceDataSource,
datasources.NewWorkspaceRoleDataSource,
}
}
// Resources defines the resources implemented in the provider.
func (p *PrefectProvider) Resources(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
resources.NewAccountResource,
resources.NewFlowResource,
resources.NewDeploymentResource,
resources.NewServiceAccountResource,
resources.NewVariableResource,
resources.NewWorkPoolResource,
resources.NewWorkspaceAccessResource,
resources.NewWorkspaceResource,
resources.NewWorkspaceRoleResource,
resources.NewBlockResource,
resources.NewBlockAccessResource,
}
}