Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# Dear Agent

This is a CLI tool for the Honeycomb.io API, written in Go.

## Structure

- `main.go` - entry point
- `cmd/` - cobra command definitions (root, version, etc.)
- `honeycomb/` - API client package

## Install

```shell
go install github.com/maragudk/honeycomb-cli@latest
```
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
# template
# honeycomb-cli

[![Docs](https://pkg.go.dev/badge/maragu.dev/template)](https://pkg.go.dev/maragu.dev/template)
[![CI](https://github.com/maragudk/template/actions/workflows/ci.yml/badge.svg)](https://github.com/maragudk/template/actions/workflows/ci.yml)
[![CI](https://github.com/maragudk/honeycomb-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/maragudk/honeycomb-cli/actions/workflows/ci.yml)

Made with ✨sparkles✨ by [maragu](https://www.maragu.dev/): independent software consulting for cloud-native Go apps & AI engineering.
A command-line interface for the [Honeycomb.io](https://www.honeycomb.io/) observability platform.

[Contact me at markus@maragu.dk](mailto:markus@maragu.dk) for consulting work, or perhaps an invoice to support this project?
Made with sparkles by [maragu](https://www.maragu.dev/).

## Install

```shell
go install github.com/maragudk/honeycomb-cli@latest
```

## Usage

```shell
# Set your API key
export HONEYCOMB_API_KEY=your-api-key

# Or pass it as a flag
honeycomb-cli --api-key your-api-key <command>

# Verify your API key
honeycomb-cli auth
```
41 changes: 41 additions & 0 deletions cmd/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"

"github.com/maragudk/honeycomb-cli/honeycomb"
)

func newAuthCommand() *cobra.Command {
return &cobra.Command{
Use: "auth",
Short: "Verify your API key and show team/environment info",
RunE: func(cmd *cobra.Command, args []string) error {
key := apiKey(cmd)
if key == "" {
return fmt.Errorf("API key is required (set HONEYCOMB_API_KEY or use --api-key)")
}

c := honeycomb.NewClient(key, honeycomb.WithBaseURL(apiURL(cmd)))
auth, err := c.Auth(cmd.Context())
if err != nil {
return err
}

fmt.Fprintf(cmd.OutOrStdout(), "Team: %v\n", auth.Team.Name)

Check failure on line 27 in cmd/auth.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `fmt.Fprintf` is not checked (errcheck)
fmt.Fprintf(cmd.OutOrStdout(), "Environment: %v\n", auth.Environment.Name)

Check failure on line 28 in cmd/auth.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `fmt.Fprintf` is not checked (errcheck)

if len(auth.APIKeyAccess) > 0 {
fmt.Fprintln(cmd.OutOrStdout(), "Permissions:")

Check failure on line 31 in cmd/auth.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `fmt.Fprintln` is not checked (errcheck)
for perm, granted := range auth.APIKeyAccess {
if granted {
fmt.Fprintf(cmd.OutOrStdout(), " %v\n", perm)

Check failure on line 34 in cmd/auth.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `fmt.Fprintf` is not checked (errcheck)
}
}
}
return nil
},
}
}
70 changes: 70 additions & 0 deletions cmd/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package cmd_test

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"maragu.dev/is"

"github.com/maragudk/honeycomb-cli/cmd"
"github.com/maragudk/honeycomb-cli/honeycomb"
)

func TestAuthCommand(t *testing.T) {
t.Run("displays team and environment info", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(honeycomb.AuthResponse{
Team: honeycomb.AuthTeam{
Name: "Acme Corp",
},
Environment: honeycomb.AuthEnvironment{
Name: "Production",
},
APIKeyAccess: map[string]bool{
"events": true,
},
})
}))
defer server.Close()

var buf bytes.Buffer
root := cmd.NewRootCommand()
root.SetOut(&buf)
root.SetArgs([]string{"auth", "--api-key", "test-key", "--api-url", server.URL})

err := root.Execute()
is.NotError(t, err)

output := buf.String()
is.True(t, contains(output, "Acme Corp"))
is.True(t, contains(output, "Production"))
is.True(t, contains(output, "events"))
})

t.Run("returns error when API key is missing", func(t *testing.T) {
root := cmd.NewRootCommand()
root.SetArgs([]string{"auth"})

// Clear the env var for this test
t.Setenv("HONEYCOMB_API_KEY", "")

err := root.Execute()
is.True(t, err != nil)
})
}

func contains(s, substr string) bool {
return len(s) >= len(substr) && searchString(s, substr)
}

func searchString(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
57 changes: 57 additions & 0 deletions cmd/columns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
"encoding/json"
"fmt"
"text/tabwriter"

"github.com/spf13/cobra"
)

func newColumnsCommand() *cobra.Command {
columnsCmd := &cobra.Command{
Use: "columns",
Short: "Manage columns in a dataset",
}

columnsCmd.PersistentFlags().String("dataset", "", "Dataset slug (required)")
_ = columnsCmd.MarkPersistentFlagRequired("dataset")

columnsCmd.AddCommand(newColumnsListCommand())

return columnsCmd
}

func newColumnsListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List columns in a dataset",
RunE: func(cmd *cobra.Command, args []string) error {
c := newClient(cmd)
dataset, _ := cmd.Flags().GetString("dataset")

columns, err := c.ListColumns(cmd.Context(), dataset)
if err != nil {
return err
}

asJSON, _ := cmd.Flags().GetBool("json")
if asJSON {
return json.NewEncoder(cmd.OutOrStdout()).Encode(columns)
}

w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "KEY NAME\tTYPE\tDESCRIPTION\tHIDDEN")

Check failure on line 44 in cmd/columns.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `fmt.Fprintln` is not checked (errcheck)
for _, col := range columns {
hidden := ""
if col.Hidden {
hidden = "yes"
}
fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", col.KeyName, col.Type, col.Description, hidden)
}
return w.Flush()
},
}
cmd.Flags().Bool("json", false, "Output as JSON")
return cmd
}
62 changes: 62 additions & 0 deletions cmd/columns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package cmd_test

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"maragu.dev/is"

"github.com/maragudk/honeycomb-cli/cmd"
"github.com/maragudk/honeycomb-cli/honeycomb"
)

func TestColumnsListCommand(t *testing.T) {
t.Run("lists columns in a table", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
is.Equal(t, "/1/columns/requests", r.URL.Path)
_ = json.NewEncoder(w).Encode([]honeycomb.Column{
{ID: "1", KeyName: "duration_ms", Type: "float", Description: "How long it took"},
{ID: "2", KeyName: "status_code", Type: "integer"},
})
}))
defer server.Close()

var buf bytes.Buffer
root := cmd.NewRootCommand()
root.SetOut(&buf)
root.SetArgs([]string{"columns", "list", "--dataset", "requests", "--api-key", "test", "--api-url", server.URL})

err := root.Execute()
is.NotError(t, err)

output := buf.String()
is.True(t, contains(output, "duration_ms"))
is.True(t, contains(output, "status_code"))
is.True(t, contains(output, "KEY NAME"))
})

t.Run("lists columns as JSON", func(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode([]honeycomb.Column{
{ID: "1", KeyName: "duration_ms", Type: "float"},
})
}))
defer server.Close()

var buf bytes.Buffer
root := cmd.NewRootCommand()
root.SetOut(&buf)
root.SetArgs([]string{"columns", "list", "--dataset", "requests", "--json", "--api-key", "test", "--api-url", server.URL})

err := root.Execute()
is.NotError(t, err)

var columns []honeycomb.Column
is.NotError(t, json.Unmarshal(buf.Bytes(), &columns))
is.Equal(t, 1, len(columns))
is.Equal(t, "duration_ms", columns[0].KeyName)
})
}
Loading
Loading