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
}
129 changes: 129 additions & 0 deletions cmd/datasets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package cmd

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

"github.com/spf13/cobra"

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

func newDatasetsCommand() *cobra.Command {
datasetsCmd := &cobra.Command{
Use: "datasets",
Short: "Manage datasets",
}

datasetsCmd.AddCommand(newDatasetsListCommand())
datasetsCmd.AddCommand(newDatasetsGetCommand())
datasetsCmd.AddCommand(newDatasetsCreateCommand())
datasetsCmd.AddCommand(newDatasetsDeleteCommand())

return datasetsCmd
}

func newDatasetsListCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "List all datasets",
RunE: func(cmd *cobra.Command, args []string) error {
c := newClient(cmd)

datasets, err := c.ListDatasets(cmd.Context())
if err != nil {
return err
}

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

w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "NAME\tSLUG\tDESCRIPTION\tLAST WRITTEN")

Check failure on line 45 in cmd/datasets.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `fmt.Fprintln` is not checked (errcheck)
for _, d := range datasets {
fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", d.Name, d.Slug, d.Description, d.LastWrittenAt)
}
return w.Flush()
},
}
cmd.Flags().Bool("json", false, "Output as JSON")
return cmd
}

func newDatasetsGetCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "get <slug>",
Short: "Get a dataset by slug",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c := newClient(cmd)

dataset, err := c.GetDataset(cmd.Context(), args[0])
if err != nil {
return err
}

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

fmt.Fprintf(cmd.OutOrStdout(), "Name: %v\n", dataset.Name)
fmt.Fprintf(cmd.OutOrStdout(), "Slug: %v\n", dataset.Slug)
fmt.Fprintf(cmd.OutOrStdout(), "Description: %v\n", dataset.Description)
fmt.Fprintf(cmd.OutOrStdout(), "Last written: %v\n", dataset.LastWrittenAt)
return nil
},
}
cmd.Flags().Bool("json", false, "Output as JSON")
return cmd
}

func newDatasetsCreateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Create a dataset",
RunE: func(cmd *cobra.Command, args []string) error {
c := newClient(cmd)

name, _ := cmd.Flags().GetString("name")
description, _ := cmd.Flags().GetString("description")

dataset, err := c.CreateDataset(cmd.Context(), honeycomb.CreateDatasetRequest{
Name: name,
Description: description,
})
if err != nil {
return err
}

fmt.Fprintf(cmd.OutOrStdout(), "Created dataset %q (%v)\n", dataset.Name, dataset.Slug)
return nil
},
}
cmd.Flags().String("name", "", "Dataset name")
_ = cmd.MarkFlagRequired("name")
cmd.Flags().String("description", "", "Dataset description")
return cmd
}

func newDatasetsDeleteCommand() *cobra.Command {
return &cobra.Command{
Use: "delete <slug>",
Short: "Delete a dataset",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c := newClient(cmd)

if err := c.DeleteDataset(cmd.Context(), args[0]); err != nil {
return err
}

fmt.Fprintf(cmd.OutOrStdout(), "Deleted dataset %q\n", args[0])
return nil
},
}
}
Loading
Loading