Repository: honeycombio/terraform-provider-honeycombio (language: python)

Issue:
data source: auth metadata
Build a new data source to query the data from the `/1/auth` endpoint and make it available to Terraform configurations (potential name `honeycombio_auth_metadata`?)

Would expose at minimum the team and environment names and slugs. Unclear if the key permissions are of interest?


Discussion / hints:

Fix patch:
diff --git a/client/auth.go b/client/auth.go
new file mode 100644
index 00000000..e39455e4
--- /dev/null
+++ b/client/auth.go
@@ -0,0 +1,53 @@
+package client
+
+import (
+	"context"
+)
+
+// The Auth endpoint lists authorizations that have been granted for an API key
+// within a team and environment.
+//
+// API docs: https://docs.honeycomb.io/api/auth/
+type Auth interface {
+	// List all authorizations for this API key in this team and environment.
+	List(ctx context.Context) (AuthMetadata, error)
+}
+
+// auth implements Auth.
+type auth struct {
+	client *Client
+}
+
+// Compile-time proof of interface implementation by type auth.
+var _ Auth = (*auth)(nil)
+
+type AuthMetadata struct {
+	// Authorizations granted to this API key.
+	APIKeyAccess struct {
+		Boards         bool `json:"boards"`
+		Columns        bool `json:"columns"`
+		CreateDatasets bool `json:"create_datasets"`
+		Events         bool `json:"events"`
+		Markers        bool `json:"markers"`
+		Queries        bool `json:"queries"`
+		Recipients     bool `json:"recipients"`
+		SLOs           bool `json:"slos"`
+		Triggers       bool `json:"triggers"`
+	} `json:"api_key_access"`
+	Environment struct {
+		// Name is empty for Classic environments.
+		Name string `json:"name"`
+		// Slug is empty for Classic environments.
+		Slug string `json:"slug"`
+	} `json:"environment"`
+	Team struct {
+		Name string `json:"name"`
+		Slug string `json:"slug"`
+	} `json:"team"`
+}
+
+func (s *auth) List(ctx context.Context) (AuthMetadata, error) {
+	var r AuthMetadata
+	err := s.client.performRequest(ctx, "GET", "/1/auth", nil, &r)
+	return r, err
+}
diff --git a/client/client.go b/client/client.go
index 514f9045..f6acbb57 100644
--- a/client/client.go
+++ b/client/client.go
@@ -61,6 +61,7 @@ type Client struct {
 	userAgent  string
 	httpClient *http.Client
 
+	Auth               Auth
 	Boards             Boards
 	Columns            Columns
 	Datasets           Datasets
@@ -101,6 +102,7 @@ func NewClient(config *Config) (*Client, error) {
 		userAgent:  cfg.UserAgent,
 		httpClient: httpClient,
 	}
+	client.Auth = &auth{client: client}
 	client.Boards = &boards{client: client}
 	client.Columns = &columns{client: client}
 	client.Datasets = &datasets{client: client}
@@ -119,6 +121,17 @@ func NewClient(config *Config) (*Client, error) {
 	return client, nil
 }
 
+// IsClassic returns true if the client is configured with a Classic API Key
+//
+// If there is an error fetching the auth metadata, this will return false.
+func (c *Client) IsClassic(ctx context.Context) bool {
+	metadata, err := c.Auth.List(ctx)
+	if err != nil {
+		return false
+	}
+	return metadata.Environment.Slug == ""
+}
+
 // ErrNotFound is returned when the requested item could not be found.
 var ErrNotFound = errors.New("404 Not Found")
 
diff --git a/docs/data-sources/auth_metadata.md b/docs/data-sources/auth_metadata.md
new file mode 100644
index 00000000..360bbff2
--- /dev/null
+++ b/docs/data-sources/auth_metadata.md
@@ -0,0 +1,48 @@
+# Data Source: honeycombio_auth_metadata
+
+The `honeycombio_auth_metadata` data source retreives information about the API key used to authenticate the provider.
+
+## Example Usage
+
+```hcl
+data "honeycombio_auth_metadata" "current" {}
+
+output "team_name" {
+  value = honeycombio_auth_metadata.current.team.name
+}
+
+output "environment_slug" {
+  value = honeycombio_auth_metadata.current.environment.slug
+} 
+
+output "slo_management_access" {
+  value = honeycombio_auth_metadata.current.api_key_access.slos
+}
+```
+
+## Argument Reference
+
+There are no arguments available for this data source.
+
+## Attribute Reference
+
+This data source exports the following attributes in addition to the arguments above:
+
+* `api_key_access` - The authorizations granted for the API key used to authenticate the provider.
+  See [the documentation](https://docs.honeycomb.io/working-with-your-data/settings/api-keys/) for more information.
+  * `boards` - `true` if this API key can create and manage Boards.
+  * `columns` - `true` if this API key can create and manage can create and manage Queries, Columns, Derived Columns, and Query Annotations
+  * `datasets` - `true` if this API key can create and manage Datasets.
+  * `events` - `true` if this API key can key can send events to Honeycomb.
+  * `markers` - `true` if this API key can create and manage Markers.
+  * `queries` - `true` if this API key can execute existing Queries via the Query Data API.
+  * `recipients` - `true` if this API key can create and manage Recipients.
+  * `slos` - `true` if this API key can create and manage SLOs.
+  * `triggers` - `true` if this API key can create and manage Triggers.
+* `environment` - Information about the Environment the API key is scoped to.
+  * `classic` - `true` if this API key belongs to a [Honeycomb Classic](https://docs.honeycomb.io/honeycomb-classic/) environment.
+  * `name` - The name of the Environment. For Classic environments, this will be null.
+  * `slug` - The slug of the Environment. For Classic environments, this will be null.
+* `team` - Information about the Team the API key is scoped to.
+  * `name` - The name of the Team.
+  * `slug` - The slug of the Team.
diff --git a/internal/provider/auth_metadata_data_source.go b/internal/provider/auth_metadata_data_source.go
new file mode 100644
index 00000000..10261b56
--- /dev/null
+++ b/internal/provider/auth_metadata_data_source.go
@@ -0,0 +1,203 @@
+package provider
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-framework/datasource"
+	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+
+	"github.com/honeycombio/terraform-provider-honeycombio/client"
+)
+
+// Ensure the implementation satisfies the expected interfaces.
+var (
+	_ datasource.DataSource              = &authMetadataDataSource{}
+	_ datasource.DataSourceWithConfigure = &authMetadataDataSource{}
+)
+
+func NewAuthMetadataDataSource() datasource.DataSource {
+	return &authMetadataDataSource{}
+}
+
+// authMetadataDataSource is the data source implementation.
+type authMetadataDataSource struct {
+	client *client.Client
+}
+
+type authMetadataDataSourceModel struct {
+	APIKeyAccess struct {
+		Boards     types.Bool `tfsdk:"boards"`
+		Columns    types.Bool `tfsdk:"columns"`
+		Datasets   types.Bool `tfsdk:"datasets"`
+		Events     types.Bool `tfsdk:"events"`
+		Markers    types.Bool `tfsdk:"markers"`
+		Queries    types.Bool `tfsdk:"queries"`
+		Recipients types.Bool `tfsdk:"recipients"`
+		SLOs       types.Bool `tfsdk:"slos"`
+		Triggers   types.Bool `tfsdk:"triggers"`
+	} `tfsdk:"api_key_access"`
+	Environment struct {
+		Classic types.Bool   `tfsdk:"classic"`
+		Name    types.String `tfsdk:"name"`
+		Slug    types.String `tfsdk:"slug"`
+	} `tfsdk:"environment"`
+	Team struct {
+		Name types.String `tfsdk:"name"`
+		Slug types.String `tfsdk:"slug"`
+	} `tfsdk:"team"`
+}
+
+func (d *authMetadataDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = req.ProviderTypeName + "_auth_metadata"
+}
+
+func (d *authMetadataDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	resp.Schema = schema.Schema{
+		Description: "Retreives information about the API key used to authenticate the provider.",
+		Blocks: map[string]schema.Block{
+			"api_key_access": schema.SingleNestedBlock{
+				Description: "The authorizations granted for the API key used to authenticate the provider. See https://docs.honeycomb.io/working-with-your-data/settings/api-keys/ for more information.",
+				Attributes: map[string]schema.Attribute{
+					"boards": schema.BoolAttribute{
+						Description: "Whether this API key can create and manage Boards.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"columns": schema.BoolAttribute{
+						Description: "Whether this API key can create and manage Queries, Columns, Derived Columns, and Query Annotations.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"datasets": schema.BoolAttribute{
+						Description: "Whether this API key can create and manage Datasets.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"events": schema.BoolAttribute{
+						Description: "Whether this API key can send events to Honeycomb.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"markers": schema.BoolAttribute{
+						Description: "Whether this API key can create and manage Markers.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"queries": schema.BoolAttribute{
+						Description: "Whether this API key can execute existing Queries via the Query Data API.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"recipients": schema.BoolAttribute{
+						Description: "Whether this API key can create and manage Recipients.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"slos": schema.BoolAttribute{
+						Description: "Whether this API key can create and manage SLOs and Burn Alerts.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"triggers": schema.BoolAttribute{
+						Description: "Whether this API key can create and manage Triggers.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+				},
+			},
+			"environment": schema.SingleNestedBlock{
+				Description: "Information about the Environment the API key is scoped to.",
+				Attributes: map[string]schema.Attribute{
+					"classic": schema.BoolAttribute{
+						Description: "Whether the Environment is a Classic Environment.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"name": schema.StringAttribute{
+						Description: "The name of the Environment. For Classic environments, this will be null.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"slug": schema.StringAttribute{
+						Description: "The slug of the Environment. For Classic environments, this will be null.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+				},
+			},
+			"team": schema.SingleNestedBlock{
+				Description: "Information about the Team the API key is scoped to.",
+				Attributes: map[string]schema.Attribute{
+					"name": schema.StringAttribute{
+						Description: "The name of the Team.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+					"slug": schema.StringAttribute{
+						Description: "The slug of the Team.",
+						Computed:    true,
+						Optional:    false,
+						Required:    false,
+					},
+				},
+			},
+		},
+	}
+}
+
+func (d *authMetadataDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) {
+	d.client = getClientFromDatasourceRequest(&req)
+}
+
+func (d *authMetadataDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var data authMetadataDataSourceModel
+
+	metadata, err := d.client.Auth.List(ctx)
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Unable to list Auth Metadata",
+			err.Error())
+		return
+	}
+
+	data.APIKeyAccess.Boards = types.BoolValue(metadata.APIKeyAccess.Boards)
+	data.APIKeyAccess.Columns = types.BoolValue(metadata.APIKeyAccess.Columns)
+	data.APIKeyAccess.Datasets = types.BoolValue(metadata.APIKeyAccess.CreateDatasets)
+	data.APIKeyAccess.Events = types.BoolValue(metadata.APIKeyAccess.Events)
+	data.APIKeyAccess.Markers = types.BoolValue(metadata.APIKeyAccess.Markers)
+	data.APIKeyAccess.Queries = types.BoolValue(metadata.APIKeyAccess.Queries)
+	data.APIKeyAccess.Recipients = types.BoolValue(metadata.APIKeyAccess.Recipients)
+	data.APIKeyAccess.SLOs = types.BoolValue(metadata.APIKeyAccess.SLOs)
+	data.APIKeyAccess.Triggers = types.BoolValue(metadata.APIKeyAccess.Triggers)
+
+	data.Team.Name = types.StringValue(metadata.Team.Name)
+	data.Team.Slug = types.StringValue(metadata.Team.Slug)
+
+	// Classic environments don't have a name or slug
+	if metadata.Environment.Slug == "" {
+		data.Environment.Classic = types.BoolValue(true)
+		data.Environment.Name = types.StringNull()
+		data.Environment.Slug = types.StringNull()
+	} else {
+		data.Environment.Classic = types.BoolValue(false)
+		data.Environment.Name = types.StringValue(metadata.Environment.Name)
+		data.Environment.Slug = types.StringValue(metadata.Environment.Slug)
+	}
+
+	diags := resp.State.Set(ctx, &data)
+	resp.Diagnostics.Append(diags...)
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index a1b8f0f9..06184e3a 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -68,6 +68,7 @@ func (p *HoneycombioProvider) Resources(ctx context.Context) []func() resource.R
 
 func (p *HoneycombioProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
 	return []func() datasource.DataSource{
+		NewAuthMetadataDataSource,
 		NewDerivedColumnDataSource,
 		NewDerivedColumnsDataSource,
 		NewSLODataSource,


Test patch:
diff --git a/client/auth_test.go b/client/auth_test.go
new file mode 100644
index 00000000..35946082
--- /dev/null
+++ b/client/auth_test.go
@@ -0,0 +1,30 @@
+package client
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAuthMetadata(t *testing.T) {
+	ctx := context.Background()
+	c := newTestClient(t)
+
+	t.Run("List", func(t *testing.T) {
+		metadata, err := c.Auth.List(ctx)
+
+		assert.NoError(t, err)
+		assert.NotEmpty(t, metadata.APIKeyAccess)
+		assert.NotEmpty(t, metadata.Team.Name)
+		assert.NotEmpty(t, metadata.Team.Slug)
+		// classic environment's don't have environment name and slugs
+		if len(c.apiKey) == 32 {
+			assert.Empty(t, metadata.Environment.Name)
+			assert.Empty(t, metadata.Environment.Slug)
+		} else {
+			assert.NotEmpty(t, metadata.Environment.Name)
+			assert.NotEmpty(t, metadata.Environment.Slug)
+		}
+	})
+}
diff --git a/client/client_test.go b/client/client_test.go
index f4da1e1c..7431e404 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -89,3 +89,10 @@ func TestClient_parseHoneycombioError(t *testing.T) {
 
 	assert.Error(t, err, "400 Bad Request: request body should not be empty")
 }
+
+func TestClient_IsClassic(t *testing.T) {
+	ctx := context.Background()
+	c := newTestClient(t)
+
+	assert.Equal(t, len(c.apiKey) == 32, c.IsClassic(ctx))
+}
diff --git a/internal/provider/auth_metadata_data_source_test.go b/internal/provider/auth_metadata_data_source_test.go
new file mode 100644
index 00000000..95092e25
--- /dev/null
+++ b/internal/provider/auth_metadata_data_source_test.go
@@ -0,0 +1,43 @@
+package provider
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestAcc_AuthMetadataData(t *testing.T) {
+	ctx := context.Background()
+	c := testAccClient(t)
+
+	metadata, err := c.Auth.List(ctx)
+	require.NoError(t, err)
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:                 testAccPreCheck(t),
+		ProtoV5ProviderFactories: testAccProtoV5ProviderFactory,
+		Steps: []resource.TestStep{
+			{
+				Config: `data "honeycombio_auth_metadata" "test" {}`,
+				Check: resource.ComposeAggregateTestCheckFunc(
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "team.name", metadata.Team.Name),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "team.slug", metadata.Team.Slug),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "environment.classic", fmt.Sprintf("%v", c.IsClassic(ctx))),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.boards", fmt.Sprintf("%v", metadata.APIKeyAccess.Boards)),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.columns", fmt.Sprintf("%v", metadata.APIKeyAccess.Columns)),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.datasets", fmt.Sprintf("%v", metadata.APIKeyAccess.CreateDatasets)),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.events", fmt.Sprintf("%v", metadata.APIKeyAccess.Events)),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.markers", fmt.Sprintf("%v", metadata.APIKeyAccess.Markers)),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.queries", fmt.Sprintf("%v", metadata.APIKeyAccess.Queries)),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.recipients", fmt.Sprintf("%v", metadata.APIKeyAccess.Recipients)),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.slos", fmt.Sprintf("%v", metadata.APIKeyAccess.SLOs)),
+					resource.TestCheckResourceAttr("data.honeycombio_auth_metadata.test", "api_key_access.triggers", fmt.Sprintf("%v", metadata.APIKeyAccess.Triggers)),
+				),
+			},
+		},
+	})
+}