mcpField
D
datasource-provisioning

datasource-provisioning

github.com/grafanaCloud & DevOps295344 installsUpdated 1d ago
Repository

About datasource-provisioning

A skill for provisioning Grafana data sources, aimed at users who need YAML configuration or Terraform resources. It resolves plugin identifiers, versions, and fields from the catalog and schema, and can codify an existing source when the Grafana MCP server is available.

Key features

  1. 01Look up plugin catalog entries and versions
  2. 02Read data source configuration schemas
  3. 03Generate YAML or Terraform configuration
  4. 04Codify an existing data source
  5. 05Represent secrets with environment placeholders

Use cases

  1. 01Create a Grafana data source configuration
  2. 02Set authentication and connection fields from a schema
  3. 03Bring an existing data source under configuration management

MCP support

For converting an existing data source, the skill uses the Grafana MCP server's list_datasources and get_datasource tools to read non-secret configuration. This conversion path is unavailable if that server is not connected.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/grafana/skills --skill datasource-provisioning

After installing, type/datasource-provisioningin Claude Code to confirm it loaded.

Details

From the project README

Workflow

1. Ask the starting point: from scratch, or from an existing data source?

Ask this before anything else (skip only if the user already made it clear):

  • From scratch — the user names a plugin type to provision → continue with step 2.
  • From an existing data source in a running instance → jump to Convert an existing data source, then return to step 6.
2. Resolve the full plugin id

Provisioning needs the canonical plugin id (<org>-<name>-datasource), not the short name a user might say.

  • Already canonical (contains -datasource or -app)? Use as-is: yesoreyeram-infinity-datasource.
  • Short name only (e.g. infinity, clickhouse)? Search the catalog API with filter=<keyword>:
    curl -s "https://grafana.com/api/plugins?filter=infinity" \
      | jq -r '.items[] | "\(.slug)\t\(.name)"'
    # → yesoreyeram-infinity-datasource    Infinity
    
    Multiple matches → show the candidates and ask which one.

The snippets below use Infinity (yesoreyeram-infinity-datasource) as the worked example — substitute the id resolved here (and the version from step 3) in every command and output.

3. Resolve the latest version
curl -s "https://grafana.com/api/plugins/yesoreyeram-infinity-datasource" | jq -r '.version'

Never hardcode a version — the CDN path is version-pinned and a stale version 404s.

4. Fetch the settings schema (primary structured source)
https://plugins-cdn.grafana.net/<PLUGIN_ID>/<VERSION>/public/plugins/<PLUGIN_ID>/schema/dsconfig.json
ID=yesoreyeram-infinity-datasource
VER=$(curl -s "https://grafana.com/api/plugins/$ID" | jq -r '.version')
curl -sf "https://plugins-cdn.grafana.net/$ID/$VER/public/plugins/$ID/schema/dsconfig.json"

This file conforms to the dsconfig schema spec — the source of truth for how to interpret it. Don't re-derive field semantics from memory (valueType alone spans string, number, boolean, array, object, map, any); consult the spec when a field isn't a plain scalar:

What you need from each field to provision: key (the provisioning key), valueType, target (root | jsonData | secureJsonData), and validations (honor allowedValues for selectors like auth_method). Orientation example (schemaVersion: "v1"):

{
  "pluginType": "yesoreyeram-infinity-datasource",
  "fields": [
    {
      "key": "auth_method",
      "valueType": "string",
      "target": "jsonData",
      "validations": [
        {
          "type": "allowedValues",
          "values": [
            "none",
            "basicAuth",
            "apiKey",
            "bearerToken",
            "oauth2",
            "aws",
            "azureBlob"
          ]
        }
      ]
    }
  ]
}

Select only the fields relevant to what the user asked for (chosen auth method + connection), not all of them. Each field's description tells you which auth method it belongs to.

For ready-made example configs, fetch v0alpha1.json:

https://plugins-cdn.grafana.net/<PLUGIN_ID>/<VERSION>/public/plugins/<PLUGIN_ID>/schema/v0alpha1.json
ID=yesoreyeram-infinity-datasource
VER=$(curl -s "https://grafana.com/api/plugins/$ID" | jq -r '.version')
curl -sf "https://plugins-cdn.grafana.net/$ID/$VER/public/plugins/$ID/schema/v0alpha1.json"

Worked examples live under settingsExamples.examples, an object keyed by scenario (e.g. apiKey, oauth2ClientCredentials). Each entry has a summary/description (the scenario) and a value holding the jsonData/secureJsonData payload to lift straight into the file:

# list scenarios, then pull one payload
... | jq -r '.settingsExamples.examples | keys[]'
... | jq '.settingsExamples.examples.apiKey.value'
5. Fallback when no schema is published

If schema/dsconfig.json 404s (older plugins):

  • Last resort: the generic structure in grafana-oss (§ Data source provisioning) — tell the user the field names are best-effort, not plugin-authoritative.
6. Map each field by its target
targetYAMLTerraform (grafana_data_source)
roottop-level key on the datasource (url, basicAuth, basicAuthUser)top-level argument (url) / inside json_data_encoded
jsonDataunder jsonData:key inside json_data_encoded = jsonencode({ … })
secureJsonDataunder secureJsonData: as ${ENV_VAR}key inside secure_json_data_encoded = jsonencode({ … }) via a sensitive variable

Use each field's valueType for the scalar (string quoted in YAML, boolean→true/false, number bare). Never inline a real secret. Nested objects (oauth2, aws) and arrays (allowedHosts, scopes) map directly.

Always set access (root target) and default it to proxy — queries route through the Grafana server (the secure default); only use direct (browser → data source) if the user explicitly asks for it. In Terraform the argument is access_mode.

7. Ask the format, then emit the file

Now ask: YAML or Terraform? Same fields, different output file and syntax. Don't assume: "provision X" may mean either; skip the question only if the user already named a format ("terraform for X"). YAML file provisioning is the native, zero-dependency path; Terraform needs the official grafana/grafana provider.

ChoiceProduces
YAML config fileprovisioning/datasources/<name>.yaml
Terraform<name>.tf (grafana_data_source resource)

<name> is just the file's basename — cosmetic, since both loaders read every file in the directory regardless of name. Default it to the plugin name.

YAML → provisioning/datasources/<name>.yaml:

apiVersion: 1
datasources:
  - name: Infinity # must be unique across the instance — collides even with a different datasource type
    type: yesoreyeram-infinity-datasource # = pluginType from the schema
    access: proxy # always set; default proxy (route queries through the Grafana server)
    uid: infinity-ds # also unique and immutable so dashboards can reference it
    jsonData:
      auth_method: apiKey # value from validations.allowedValues
      apiKeyKey: X-API-Key
      apiKeyType: header
      allowedHosts:
        - https://api.example.com
    secureJsonData:
      apiKeyValue: ${API_KEY} # env var ref, never a literal secret
    editable: false

Terraform → <name>.tf:

variable "api_key" {
  type      = string
  sensitive = true
}

resource "grafana_data_source" "infinity" {
  type        = "yesoreyeram-infinity-datasource"
  name        = "Infinity"
  uid         = "infinity-ds"
  access_mode = "proxy" # always set; default proxy (route queries through the Grafana server)

  json_data_encoded = jsonencode({
    auth_method  = "apiKey"
    apiKeyKey    = "X-API-Key"
    apiKeyType   = "header"
    allowedHosts = ["https://api.example.com"]
  })

  secure_json_data_encoded = jsonencode({
    apiKeyValue = var.api_key
  })
}

grafana_data_source is from the grafana/grafana provider — the authoritative reference for argument names (access_mode, json_data_encoded, secure_json_data_encoded). This file is only the resource; the user supplies the required_providers + provider "grafana" block and credentials.

8. Return the file to the user

Present the complete file in a single code block for the user to copy and paste into their environment — note where it goes:

  • YAML → provisioning/datasources/<name>.yaml (apply on Grafana start or a provisioning reload).
  • Terraform → their Terraform config, applied with terraform apply.

Optionally, tell them how to confirm it worked once applied:

curl -s https://grafana.example.com/api/datasources/uid/<uid>/health \
  -H "Authorization: Bearer <token>"
# { "status": "OK" }    → working
# { "status": "ERROR" } → URL unreachable or auth misconfigured

Or verify in the UI: visit <https://grafana.example.com>/connections/datasources/edit/<uid> and click Test.

Convert an existing data source

To codify a data source already configured in a running instance, read its config through the Grafana MCP server (grafana/mcp-grafana).

Precondition: the Grafana MCP server is connected with its Datasources toolset enabled (it holds the instance credentials). If it isn't available, do not support this path — never ask the user to paste a Grafana token into chat. Fall back to the from-scratch Workflow instead.

  1. Find the data source with the MCP tools — list_datasources to browse, then get_datasource (by uid or name) for the full config.
  2. The result carries every non-secret field directly: type, uid, url, access, basicAuth, basicAuthUser, and the full jsonData object. Copy them as-is.
  3. Secrets are never returned. The secureJsonFields map lists which secret keys are set (e.g. {"apiKeyValue": true}) without their values. Emit an ${ENV_VAR} placeholder in secureJsonData for each key it reports true.
  4. Cross-check against the schema (step 4) to confirm secret key names and target placement, then continue at step 6 (map) and step 7 (emit) as normal.

Related

  • grafana-oss — generic data source / dashboard provisioning structure and provisioning paths.

Comments

Markdown supported