Terraform 自定义提供程序 - 数据源架构

Terraform Custom Provider - Data Source Schema

我正在使用 Terraform SDK 创建自定义 Terraform 提供程序。我正在尝试从现有的 API GET 调用中读取数据。我发现很难将 JSON 响应从 API 映射到 terraform 模式。这是我的数据源模式:

func dataSourceProjects() *schema.Resource {
  return &schema.Resource{
    ReadContext: dataSourceProjectsRead,
    Schema: map[string]*schema.Schema{
      "members": &schema.Schema{
        Type:     schema.TypeList,
        Elem:     &schema.Schema{Type: schema.TypeString},
        Computed: true,
      },
      "owners": &schema.Schema{
        Type:     schema.TypeList,
        Elem:     &schema.Schema{Type: schema.TypeString},
        Computed: true,
      },
    },
  }
}

这是 API JSON 响应:

{
  "members": [
    "test12",
    "test8800",
    "test0032",
    "test1234"
  ],
  "owners": [
    "test000",
    "test111",
    "test12",
    "test1234"
  ]
}

这是我的数据源读取函数

func dataSourceProjectsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {

  client := &http.Client{Timeout: 10 * time.Second}

  // Warning or errors can be collected in a slice type
  var diags diag.Diagnostics

  req, err := http.NewRequest("GET", fmt.Sprintf("%s/test/team", "https://myurl/v1"), nil)
  req.Header.Add("Authorization", "Bearer xxxxx")
  if err != nil {
    return diag.FromErr(err)
  }

  r, err := client.Do(req)
  if err != nil {
    return diag.FromErr(err)
  }
  defer r.Body.Close()
  members := make([]string, 0)
  err = json.NewDecoder(r.Body).Decode(&members)
  if err != nil {
    return diag.FromErr(err)
  }

  if err := d.Set("members", members); err != nil {
    return diag.FromErr(err)
  }

  // always run
  d.SetId(strconv.FormatInt(time.Now().Unix(), 10))

  return diags
}

我不断收到此错误:

Error: json: cannot unmarshal object into Go value of type []string

server.go

package main

import (
    "log"
    "net/http"
)

func main() {
    s := `
    {
      "members": [
        "test12",
        "test8800",
        "test0032",
        "test1234"
      ],
      "owners": [
        "test000",
        "test111",
        "test12",
        "test1234"
      ]
    }
    `

    http.HandleFunc("/projects", func(w http.ResponseWriter, _ *http.Request) {
      log.Println("Getting Projects")
      w.WriteHeader(http.StatusOK)
      w.Write([]byte(s))
    })

    log.Println("Listening...")
    log.Fatal(http.ListenAndServe(":8000", nil))
}

data_source_projects.go

package hashicups

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"
    "time"

    "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
    "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataSourceProjects() *schema.Resource {
  return &schema.Resource{
    ReadContext: dataSourceProjectsRead,
    Schema: map[string]*schema.Schema{
      "members": &schema.Schema{
        Type:     schema.TypeList,
        Elem:     &schema.Schema{Type: schema.TypeString},
        Computed: true,
      },
      "owners": &schema.Schema{
        Type:     schema.TypeList,
        Elem:     &schema.Schema{Type: schema.TypeString},
        Computed: true,
      },
    },
  }
}

func dataSourceProjectsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
    client := &http.Client{Timeout: 10 * time.Second}

    // Warning or errors can be collected in a slice type
    var diags diag.Diagnostics

    req, err := http.NewRequest("GET", fmt.Sprintf("%s/projects", "http://localhost:8000"), nil)
    if err != nil {
        return diag.FromErr(err)
    }

    r, err := client.Do(req)
    if err != nil {
        return diag.FromErr(err)
    }
    defer r.Body.Close()

    var projects map[string]interface{}
    err = json.NewDecoder(r.Body).Decode(&projects)
    if err != nil {
        return diag.FromErr(err)
    }

    if err := d.Set("members", projects["members"]); err != nil {
        return diag.FromErr(err)
    }

    if err := d.Set("owners", projects["owners"]); err != nil {
        return diag.FromErr(err)
    }

    // always run
    d.SetId(strconv.FormatInt(time.Now().Unix(), 10))

    return diags
}

输出:

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


Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

project = {
  "id" = "1651575329"
  "members" = tolist([
    "test12",
    "test8800",
    "test0032",
    "test1234",
  ])
  "owners" = tolist([
    "test000",
    "test111",
    "test12",
    "test1234",
  ])
}

正如 Matt 和提到的错误代码,您正在尝试将您的正文解组为不正确的结构。

基本上,您在 Body 中没有数组,您有一个包含字符串数组的字段的对象。

您的解组方式适用于像

这样的主体
["test12", "test23", "test34"]

尝试如下解码,因此使用具有显式映射的结构。

package main

import (
    "encoding/json"
    "fmt"
)

type Schema struct {
    Members []string `json:"members"`
    Owners  []string `json:"owners"`
}

var jsonBody = `{
  "members": [
    "test12",
    "test8800",
    "test0032",
    "test1234"
  ],
  "owners": [
    "test000",
    "test111",
    "test12",
    "test1234"
  ]
}`

func main() {
    var s Schema
    err := json.Unmarshal([]byte(jsonBody), &s)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(s)
    }

}