Terraform是目前比较火的Ias工具,可以实现多云,多资源的管理。目前其提供的peovider已经是人人都可以写,人人都可以用了。那么作为一个资源提供者,如何让用户便捷使用和管理自己的资源已经是资源提供者首要考虑的问题。那么作为开发者,如何开发一款自己的provider呢?本文以framwork为着手点进行介绍。
什么是provider?故名思义,就是提供者,是资源的提供者,数据的提供者,实际上就是资源厂商提供的插件,这个插件可以和Terraform core进行交互,同时也可以和资源提供平台通过诸如SDK/API进行数据交互。官方的流程图是这样的:
terraform core简单来说就是负责解析terraform 配置,再向provider通过grpc请求和接收响应数据的stuff。provider在执行terraform init时下载并运行,于core建立连接。于目标资源平台通过HTTP请求获取资源/执行CRUD操作。
另外要说的是,provider是用golang系的二进制文件。我们可以在官方仓库找到大部分的流行的资源的provider。
一个provider实际上包含几部分:
package main
import (
"context"
"flag"
"log"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/example_namespace/terraform-provider-example/internal/provider"
)
func main() {
var debug bool
flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
flag.Parse()
opts := providerserver.ServeOpts{
Address: "registry.terraform.io/example_namespace/example",
Debug: debug,
}
err := providerserver.Serve(context.Background(), provider.New(), opts)
if err != nil {
log.Fatal(err.Error())
}
}
每个provider必须实现一个 gRPC server,该server支持启动时特定于 Terraform 的连接和握手处理。 Terraform provider程序需要实现暴露资源和管理资源的能力。
上述代码中main函数定义了一个server。provider.New()
需要返回一个能实现 provider.Provider
接口. provider.Provider
接口必须定义能够获取data source和管理resource的方法。
provider就是封装能够提供data source和resource的方法的stuffs,云资源平台可能会提供很多API/SDK来管理资源,我们可以为每一种资源或者data提供一个单独的文件用来处理这些要求的数据。处理到的数据也可以成为一种Terraform state。
package provider
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"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-framework/types"
)
var _ provider.Provider = (*exampleProvider)(nil)
var _ provider.ProviderWithMetadata = (*exampleProvider)(nil)
type exampleProvider struct{}
func New() func() provider.Provider {
return func() provider.Provider {
return &exampleProvider{}
}
}
func (p *exampleProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
}
func (p *exampleProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "example"
}
func (p *exampleProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewDataSource,
}
}
func (p *exampleProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewResource,
}
}
func (p *exampleProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
}
这里的代码里的New()
就是main.go中被server调用的函数,按照TF plugin的协议,这里必须返回的类型必须能满足provider.Provider
的接口,即:Metadata,Configure,DataSources,Resources,Schema。
resource就是资源,对应于上述Resource。
package provider
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var _ resource.Resource = (*exampleResource)(nil)
type exampleResource struct {
provider exampleProvider
}
func NewResource() resource.Resource {
return &exampleResource{}
}
func (e *exampleResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_resource"
}
func (e *exampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"configurable_attribute": schema.StringAttribute{
Optional: true,
},
"id": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}
type exampleResourceData struct {
ConfigurableAttribute types.String `tfsdk:"configurable_attribute"`
Id types.String `tfsdk:"id"`
}
func (e *exampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data exampleResourceData
diags := req.Config.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Create resource using 3rd party API.
data.Id = types.StringValue("example-id")
tflog.Trace(ctx, "created a resource")
diags = resp.State.Set(ctx, &data)
resp.Diagnostics.Append(diags...)
}
func (e *exampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data exampleResourceData
diags := req.State.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read resource using 3rd party API.
diags = resp.State.Set(ctx, &data)
resp.Diagnostics.Append(diags...)
}
func (e *exampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data exampleResourceData
diags := req.Plan.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Update resource using 3rd party API.
diags = resp.State.Set(ctx, &data)
resp.Diagnostics.Append(diags...)
}
func (e *exampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data exampleResourceData
diags := req.State.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Delete resource using 3rd party API.
}
`NewResource()` 返回`resource.Resource` 类型的接口. `provider` 调用`NewResource()` 将会返回一个resource的实例。它同样也有Metadata,Schema方法。除此之外,要求实现CRUD方法。返回TF的state。
这是一个要求能提供读资源能力的data source对象。他跟resource非常的类似。这里不再赘述,代码示例:
package provider
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var _ datasource.DataSource = (*exampleDataSource)(nil)
type exampleDataSource struct {
provider exampleProvider
}
func NewDataSource() datasource.DataSource {
return &exampleDataSource{}
}
func (e *exampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_datasource"
}
func (e *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"configurable_attribute": schema.StringAttribute{
MarkdownDescription: "Example configurable attribute",
Optional: true,
},
"id": schema.StringAttribute{
MarkdownDescription: "Example identifier",
Computed: true,
},
},
}
}
type exampleDataSourceData struct {
ConfigurableAttribute types.String `tfsdk:"configurable_attribute"`
Id types.String `tfsdk:"id"`
}
func (e *exampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data exampleDataSourceData
diags := req.Config.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Interact with 3rd party API to read data source.
data.Id = types.StringValue("example-id")
tflog.Trace(ctx, "read a data source")
diags = resp.State.Set(ctx, &data)
resp.Diagnostics.Append(diags...)
}
Terraform官方实际提供了两种方法,方便开发者进行开发providers,一种是SDK,另外一种是上面介绍的framwork的结构,也是官方推荐的,官方也提供了step-by-step的方法,大家可以参考。
本文介绍了Terraform provider的开发框架framework,介绍了其结构组成,方便友友们快速认识,以便后期快速上手。下期我也会step-by-step提供文章进行阐述如何开发自己的provider,欢迎关注。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。