Terraform Custom Provider - Data Source Schema

942 views Asked by At

I am working on creating a custom terraform provider by using terraform sdk. I am trying to read data from the existing API GET call. I am finding it difficult to map the JSON response from an API to terraform schema. This is my data source 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,
      },
    },
  }
}

This is the API JSON response:

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

This is my Data source read function

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
}

I keep getting this error:

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

2

There are 2 answers

0
Chandan On BEST ANSWER

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
}

Output:

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",
  ])
}
0
Armatorix On

Just as Matt and the error code mentioned, you're trying to unmarshal your body to an improper struct.

Basically, you don't have an array in the Body, you have an object with fields that have an array of strings.

Your way of unmarshalling would work for bodies like

["test12", "test23", "test34"]

Try to decode it as below, so use a struct with explicit mapping.

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)
    }

}