前言
上节课我们学习了如何添加日志,这节课我们来创建和读取数据,在本教程中,您将向提供程序的新 order 资源添加创建和读取功能,该资源与名为 Hashicups 的虚构咖啡店应用程序的 API 进行交互。为此,您将:
- Define the initial resource type.
- Add the resource to the provider.
- Implement the HashiCups client in the resource.
- Define the resource's schema
- Define the resource's data model.
- Define the resource's create logic.
- Define the resource's read logic.
- Verify the resource's behavior.
实现初始资源类型
Providers 使用 resource.Resource 接口类型的实现作为资源实现的起点,此接口需要满足以下条件:
- 用于定义资源类型名称的 Metadata method,这是资源在 Terraform 配置中的使用方式。
- 一种 Schema method,用于定义任何资源配置、计划和状态数据的架构。
- 一个 Create 方法,用于定义创建资源并设置其初始 Terraform 状态的逻辑。
- 一个 Read 方法,用于定义刷新资源的 Terraform 状态的逻辑。
- 一个 Update 方法,用于定义更新资源并在成功时设置更新的 Terraform 状态的逻辑。
- 一个 Delete 方法,用于定义删除资源并在成功时删除 Terraform 状态的逻辑。
创建 internal/provider/order_resource.go 文件。
package provider
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &orderResource{}
)
// NewOrderResource is a helper function to simplify the provider implementation.
func NewOrderResource() resource.Resource {
return &orderResource{}
}
// orderResource is the resource implementation.
type orderResource struct{}
// Metadata returns the resource type name.
func (r *orderResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_order"
}
// Schema defines the schema for the resource.
func (r *orderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{}
}
// Create creates the resource and sets the initial Terraform state.
func (r *orderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
}
// Read refreshes the Terraform state with the latest data.
func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
}
// Update updates the resource and sets the updated Terraform state on success.
func (r *orderResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
}
// Delete deletes the resource and removes the Terraform state on success.
func (r *orderResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
}
向 provider 添加资源
提供程序从其 Resources 方法中返回它们支持的资源。打开 internal/provider/provider.go 文件。
通过将 Resources 该方法替换为以下方法,将 NewOrderResource 资源添加到提供程序。
实现资源客户端功能
资源使用可选 Configure 方法从提供程序获取已配置的客户端。提供程序已配置 HashiCups 客户端,并且资源可以保存对该客户端的引用以执行其操作。
打开 internal/provider/order_resource.go 文件。
允许您的资源类型存储对 HashiCups 客户端的引用,方法是将该 orderResource 类型替换为以下内容。
// orderResource is the resource implementation.
type orderResource struct {
client *hashicups.Client
}
导入资源所需的 HashiCups 客户端包,方法是将文件开头 import 的语句替换为以下内容。
import (
"context"
"github.com/hashicorp-demoapp/hashicups-client-go"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
通过将 var 语句替换为以下内容,确保您的资源满足框架定义的 Resource 和 ResourceWithConfigure 接口。
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &orderResource{}
_ resource.ResourceWithConfigure = &orderResource{}
)
使用以下命令添加检索 HashiCups 客户端 Configure 的方法
// Configure adds the provider configured client to the resource.
func (r *orderResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
client, ok := req.ProviderData.(*hashicups.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *hashicups.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
r.client = client
}
实现资源 schema
资源使用该 Schema 方法来定义支持的配置、计划和状态属性名称和类型。订单资源需要将具有各种属性的咖啡列表保存到 Terraform 的状态。
将订单资源 Schema 的方法替换为以下内容
// Schema defines the schema for the resource.
func (r *orderResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
"last_updated": schema.StringAttribute{
Computed: true,
},
"items": schema.ListNestedAttribute{
Required: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"quantity": schema.Int64Attribute{
Required: true,
},
"coffee": schema.SingleNestedAttribute{
Required: true,
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Required: true,
},
"name": schema.StringAttribute{
Computed: true,
},
"teaser": schema.StringAttribute{
Computed: true,
},
"description": schema.StringAttribute{
Computed: true,
},
"price": schema.Float64Attribute{
Computed: true,
},
"image": schema.StringAttribute{
Computed: true,
},
},
},
},
},
},
},
}
}
实现资源数据模型
将资源的以下数据模型类型添加到 order_resource.go 。
// orderResourceModel maps the resource schema data.
type orderResourceModel struct {
ID types.String `tfsdk:"id"`
Items []orderItemModel `tfsdk:"items"`
LastUpdated types.String `tfsdk:"last_updated"`
}
// orderItemModel maps order item data.
type orderItemModel struct {
Coffee orderItemCoffeeModel `tfsdk:"coffee"`
Quantity types.Int64 `tfsdk:"quantity"`
}
// orderItemCoffeeModel maps coffee order item data.
type orderItemCoffeeModel struct {
ID types.Int64 `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Teaser types.String `tfsdk:"teaser"`
Description types.String `tfsdk:"description"`
Price types.Float64 `tfsdk:"price"`
Image types.String `tfsdk:"image"`
}
实现创建功能
提供程序使用该 Create 方法基于架构数据创建新资源。
create 方法遵循以下步骤
- Checks whether the API Client is configured
- Retrieves values from the plan.
- Generates an API request body from the plan values
- Creates a new order
- Maps response body to resource schema attributes.
- Sets Terraform's state with the new order's details.
将资源 Create 的方法替换为以下内容。
// Create a new resource.
func (r *orderResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// Retrieve values from plan
var plan orderResourceModel
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Generate API request body from plan
var items []hashicups.OrderItem
for _, item := range plan.Items {
items = append(items, hashicups.OrderItem{
Coffee: hashicups.Coffee{
ID: int(item.Coffee.ID.ValueInt64()),
},
Quantity: int(item.Quantity.ValueInt64()),
})
}
// Create new order
order, err := r.client.CreateOrder(items)
if err != nil {
resp.Diagnostics.AddError(
"Error creating order",
"Could not create order, unexpected error: "+err.Error(),
)
return
}
// Map response body to schema and populate Computed attribute values
plan.ID = types.StringValue(strconv.Itoa(order.ID))
for orderItemIndex, orderItem := range order.Items {
plan.Items[orderItemIndex] = orderItemModel{
Coffee: orderItemCoffeeModel{
ID: types.Int64Value(int64(orderItem.Coffee.ID)),
Name: types.StringValue(orderItem.Coffee.Name),
Teaser: types.StringValue(orderItem.Coffee.Teaser),
Description: types.StringValue(orderItem.Coffee.Description),
Price: types.Float64Value(orderItem.Coffee.Price),
Image: types.StringValue(orderItem.Coffee.Image),
},
Quantity: types.Int64Value(int64(orderItem.Quantity)),
}
}
plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850))
// Set state to fully populated data
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
通过将 import 语句替换为以下内容来添加 Create 方法中使用的包。
import (
"context"
"fmt"
"strconv"
"time"
"github.com/hashicorp-demoapp/hashicups-client-go"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
实现读取功能
provider 使用该 Read 函数检索资源的信息,并更新 Terraform 状态以反映资源的当前状态。提供程序在每个计划之前调用此函数,以在资源的当前状态和配置之间生成准确的差异。
read 函数遵循以下步骤:
- Gets the current state
- Retrieves the order ID from Terraform's state.
- Retrieves the order details from the client.
- Maps the response body to resource schema attributes.
- Set Terraform's state with the order's details.
将提供程序 Read 的方法 order_resource.go 替换为以下内容:
// Read resource information.
func (r *orderResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
// Get current state
var state orderResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Get refreshed order value from HashiCups
order, err := r.client.GetOrder(state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error Reading HashiCups Order",
"Could not read HashiCups order ID "+state.ID.ValueString()+": "+err.Error(),
)
return
}
// Overwrite items with refreshed state
state.Items = []orderItemModel{}
for _, item := range order.Items {
state.Items = append(state.Items, orderItemModel{
Coffee: orderItemCoffeeModel{
ID: types.Int64Value(int64(item.Coffee.ID)),
Name: types.StringValue(item.Coffee.Name),
Teaser: types.StringValue(item.Coffee.Teaser),
Description: types.StringValue(item.Coffee.Description),
Price: types.Float64Value(item.Coffee.Price),
Image: types.StringValue(item.Coffee.Image),
},
Quantity: types.Int64Value(int64(item.Quantity)),
})
}
// Set refreshed state
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
生成并安装更新的提供程序。
go install .
验证资源
刚刚修改的 Terraform 提供程序已准备好与 API endpoint 通信以创建订单。
mkdir examples/order && cd "$_"
在此目录中创建一个 main.tf Terraform 配置文件,用于创建新订单。
terraform {
required_providers {
hashicups = {
source = "hashicorp.com/edu/hashicups"
}
}
required_version = ">= 1.1.0"
}
provider "hashicups" {
username = "education"
password = "test123"
host = "http://localhost:19090"
}
resource "hashicups_order" "edu" {
items = [{
coffee = {
id = 3
}
quantity = 2
}, {
coffee = {
id = 1
}
quantity = 2
}
]
}
output "edu_order" {
value = hashicups_order.edu
}
应用您的配置以创建订单。请注意执行计划如何显示建议的订单,以及有关该订单的其他信息。请记住使用 . yes
terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
hashicups_order.edu will be created
+ resource "hashicups_order" "edu" {
+ id = (known after apply)
# ...
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ edu_order = {
+ id = (known after apply)
+ items = [
+ {
#...
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
hashicups_order.edu: Creating...
hashicups_order.edu: Creation complete after 0s [id=21]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
edu_order = {
"id" = "1"
"items" = tolist([
{
"coffee" = {
"description" = ""
"id" = 3
"image" = "/vault.png"
"name" = "Vaulatte"
"price" = 200
"teaser" = "Nothing gives you a safe and secure feeling like a Vaulatte"
}
"quantity" = 2
},
{
"coffee" = {
"description" = ""
"id" = 1
"image" = "/hashicorp.png"
"name" = "HCP Aeropress"
"price" = 200
"teaser" = "Automation in a cup"
}
"quantity" = 2
},
])
"last_updated" = "Thursday, 09-Feb-23 11:32:05 EST"
}
应用完成后,提供程序会将资源的详细信息保存在 Terraform 的状态中。通过运行 terraform state show <resource_name> 查看状态。
terraform state show hashicups_order.edu
hashicups_order.edu:
resource "hashicups_order" "edu" {
id = "1"
items = [
(2 unchanged elements hidden)
]
last_updated = "Thursday, 22-Jul-21 03:26:51 PDT"
}
由于订单已成功创建,因此在 terraform apply 状态期间执行计划中 (known after apply) 的值已全部填充。
验证已创建的订单
当您使用 Terraform 在 HashiCups 中创建订单时,包含 HashiCups 日志的终端将记录 HashiCups 提供程序调用的操作。切换到该终端以查看日志消息。
api_1 | 2021-07-22T10:26:31.179Z [INFO] Handle User | signin
api_1 | 2021-07-22T10:26:51.179Z [INFO] Handle User | signin
api_1 | 2021-07-22T10:26:51.195Z [INFO] Handle Orders | CreateOrder
通过 API 检索订单详细信息来验证 Terraform 是否创建了订单
curl -X GET -H "Authorization: ${HASHICUPS_TOKEN}" localhost:19090/orders/1
代码

我是南哥,日常分享高质量文章、架构设计、前沿资讯,加微信拉粉丝交流群,和大家交流!