前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >初识Terraform Provider 开发框架

初识Terraform Provider 开发框架

原创
作者头像
金鹏
修改2024-05-14 10:10:15
1850
修改2024-05-14 10:10:15
举报
文章被收录于专栏:SDLC技术探究

Terraform是目前比较火的Ias工具,可以实现多云,多资源的管理。目前其提供的peovider已经是人人都可以写,人人都可以用了。那么作为一个资源提供者,如何让用户便捷使用和管理自己的资源已经是资源提供者首要考虑的问题。那么作为开发者,如何开发一款自己的provider呢?本文以framwork为着手点进行介绍。

一 初识provider

什么是provider?故名思义,就是提供者,是资源的提供者,数据的提供者,实际上就是资源厂商提供的插件,这个插件可以和Terraform core进行交互,同时也可以和资源提供平台通过诸如SDK/API进行数据交互。官方的流程图是这样的:

terraform core简单来说就是负责解析terraform 配置,再向provider通过grpc请求和接收响应数据的stuff。provider在执行terraform init时下载并运行,于core建立连接。于目标资源平台通过HTTP请求获取资源/执行CRUD操作。

另外要说的是,provider是用golang系的二进制文件。我们可以在官方仓库找到大部分的流行的资源的provider。

一个provider实际上包含几部分:

  • provider server: 这是一个封装的grpc server,负责与core交互
  • provider:负责于target API和server交互,承接上下层
  • resource:提供CURD操作,管理资源
  • data source: 用于读取平台资源

二 provider解构

2.1 provider server

代码语言:golang
复制
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的方法。

2.2 provider

provider就是封装能够提供data source和resource的方法的stuffs,云资源平台可能会提供很多API/SDK来管理资源,我们可以为每一种资源或者data提供一个单独的文件用来处理这些要求的数据。处理到的数据也可以成为一种Terraform state。

代码语言:golang
复制
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。

  • Schema:这个方法用来定义获取provider的参数元数据字段,他们将被用于TF的配置文件中。
  • Metadata:定义dprovider的type,就是在.tf文件中provider 字段后面的那一位stuff。
  • Configure:这个主要用来初始化credential,认证资源,这里会要求提供诸如host,token,userid/password之类的字段。
  • DataSources:这个就是对应data字段的方法,要求实现datasource.DataSource接口的read方法,用来返回get到的数据。
  • Resources:这个就是对应resource字段的方法,要求实现resource.Resource实例。

2.3 resource

resource就是资源,对应于上述Resource。

代码语言:golang
复制
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。

2.4 data source

这是一个要求能提供读资源能力的data source对象。他跟resource非常的类似。这里不再赘述,代码示例:

代码语言:golang
复制
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...)
}

2.5 如何开发自己的provider

Terraform官方实际提供了两种方法,方便开发者进行开发providers,一种是SDK,另外一种是上面介绍的framwork的结构,也是官方推荐的,官方也提供了step-by-step的方法,大家可以参考

三 总结

本文介绍了Terraform provider的开发框架framework,介绍了其结构组成,方便友友们快速认识,以便后期快速上手。下期我也会step-by-step提供文章进行阐述如何开发自己的provider,欢迎关注。

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一 初识provider
  • 二 provider解构
    • 2.1 provider server
      • 2.2 provider
        • 2.3 resource
          • 2.4 data source
            • 2.5 如何开发自己的provider
            • 三 总结
            相关产品与服务
            云资源自动化 for Terraform
            云资源自动化 for Terraform(Infrastructure Automation for Terraform)是腾讯云基于全球备受欢迎的基础设施即代码(Infrastructure as Code)工具 Terraform 插件扩展体系下,提供的腾讯云资源部署、实施和管理的自动化能力,将 Terraform 完善成熟的基础设施自动化工具能力和腾讯云的云资源服务能力相互联接,让客户可以方便地实施 DevOps 和多云部署等模式。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档