Search code examples
gosdkterraformschematerraform-provider

Terraform Custom Provider - Data Source Schema


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


Solution

  • 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",
      ])
    }