Datasource graphql (#6)

* Add graphql datasource.

* Add docs via go generate ./..
main
Mikhail Yohman 2023-05-15 01:20:23 -06:00 committed by GitHub
parent 278f60f50b
commit 05367ff225
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 208 additions and 6 deletions

2
.gitignore vendored
View File

@ -25,8 +25,10 @@ website/node_modules
*.test
*.iml
.vscode/
test/.terraform.lock.hcl
website/vendor
test/*.tfplan
# Test exclusions
!command/test-fixtures/**/*.tfstate

View File

@ -0,0 +1,27 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "nautobot_graphql Data Source - terraform-provider-nautobot"
subcategory: ""
description: |-
Provide an interface to make GraphQL calls to Nautobot as a flexible data source.
---
# nautobot_graphql (Data Source)
Provide an interface to make GraphQL calls to Nautobot as a flexible data source.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `query` (String) The GraphQL query that will be sent to Nautobot.
### Read-Only
- `data` (String) The data returned by the GraphQL query.
- `id` (String) The ID of this resource.

View File

@ -1 +1,11 @@
data "nautobot_manufacturers" "all" {}
data "nautobot_manufacturers" "all" {}
data "nautobot_graphql" "vms" {
query = <<EOF
query {
virtual_machines {
id
}
}
EOF
}

View File

@ -0,0 +1,81 @@
package provider
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/tidwall/gjson"
)
func dataSourceGraphQL() *schema.Resource {
return &schema.Resource{
Description: "Provide an interface to make GraphQL calls to Nautobot as a flexible data source.",
ReadContext: dataSourceGraphQLRead,
Schema: map[string]*schema.Schema{
"query": {
Description: "The GraphQL query that will be sent to Nautobot.",
Type: schema.TypeString,
Required: true,
},
"data": {
Description: "The data returned by the GraphQL query.",
Type: schema.TypeString,
Computed: true,
},
},
}
}
type reqBody struct {
Query string `json:"query"`
}
// Use this as reference: https://learn.hashicorp.com/tutorials/terraform/provider-setup?in=terraform/providers#implement-read
func dataSourceGraphQLRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var diags diag.Diagnostics
c := meta.(*apiClient).BaseClient
s := fmt.Sprintf("%sgraphql/", meta.(*apiClient).Server)
t := meta.(*apiClient).Token
query := d.Get("query").(string)
queryBody, _ := json.Marshal(reqBody{Query: query})
req, err := http.NewRequestWithContext(ctx, "POST", s, bytes.NewBuffer(queryBody))
if err != nil {
return diag.Errorf("failed to create request with context %s: %s", s, err.Error())
}
req.Header.Add("Content-Type", "application/json")
// Add the authorization header to our request.
t.Intercept(ctx, req)
rsp, err := c.Client.Do(req)
if err != nil {
return diag.Errorf("failed to successfully call %s: %s", s, err.Error())
}
defer rsp.Body.Close()
body, err := io.ReadAll(rsp.Body)
if err != nil {
return diag.Errorf("failed to decode GraphQL response from %s: %s", s, err.Error())
}
data := gjson.Get(string(body), "data")
if err := d.Set("data", data.Raw); err != nil {
return diag.FromErr(err)
}
// always run
d.SetId(strconv.FormatInt(time.Now().Unix(), 10))
return diags
}

View File

@ -0,0 +1,47 @@
package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)
func TestAccDataSourceGraphQL(t *testing.T) {
// https://github.com/hashicorp/terraform-plugin-sdk/issues/952
t.Skip("resource not yet implemented, remove this once you add your own code")
resource.UnitTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: testAccDataSourceGraphQL,
Check: resource.ComposeTestCheckFunc(
//resource.TestCheckResourceAttr("data.nautobot_manufacturers.list.manufacturers", "", ""),
resource.TestCheckOutput("data_source_graphql", "virtual_machines"),
),
},
},
})
}
const testAccDataSourceGraphQL = `
provider "nautobot" {
url = "https://demo.nautobot.com/api/"
token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
data "nautobot_graphql" "vms" {
query = <<EOF
query {
virtual_machines {
id
}
}
EOF
}
output "data_source_graphql" {
value = data.nautobot_graphql.vms
}
`

View File

@ -53,6 +53,7 @@ func New(version string) func() *schema.Provider {
},
DataSourcesMap: map[string]*schema.Resource{
"nautobot_manufacturers": dataSourceManufacturers(),
"nautobot_graphql": dataSourceGraphQL(),
},
ResourcesMap: map[string]*schema.Resource{
"nautobot_manufacturer": resourceManufacturer(),
@ -69,8 +70,10 @@ func New(version string) func() *schema.Provider {
// you would need to setup to communicate with the upstream
// API.
type apiClient struct {
Client *nb.ClientWithResponses
Server string
Client *nb.ClientWithResponses
Server string
Token *SecurityProviderNautobotToken
BaseClient *nb.Client
}
func configure(
@ -102,10 +105,21 @@ func configure(
diags[0].Severity = diag.Error
return &apiClient{Server: serverURL}, diags
}
bc, err := nb.NewClient(
serverURL,
nb.WithRequestEditorFn(token.Intercept),
)
if err != nil {
diags = diag.FromErr(err)
diags[0].Severity = diag.Error
return &apiClient{Server: serverURL}, diags
}
return &apiClient{
Client: c,
Server: serverURL,
Client: c,
Server: serverURL,
Token: token,
BaseClient: bc,
}, diags
}
}

View File

@ -14,4 +14,25 @@ output "data_source_example" {
manufacturer.id => manufacturer
if manufacturer.name == var.manufacturer_name
}
}
}
data "nautobot_graphql" "nodes" {
query = <<EOF
query {
virtual_machines {
name
id
}
devices {
name
id
}
}
EOF
}
output "data_source_graphql" {
value = data.nautobot_graphql.nodes
}
output "data_source_graphql_vm" {
value = jsondecode(data.nautobot_graphql.nodes.data).virtual_machines
}