Skip to content

Commit 95747c3

Browse files
authored
Merge pull request #2 from andreagrandi/add-config-support
Add configuration file support
2 parents 4fd79cf + 15e00b7 commit 95747c3

File tree

4 files changed

+412
-10
lines changed

4 files changed

+412
-10
lines changed

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,33 @@ go build -o sentire ./cmd/sentire
4949

5050
## Configuration
5151

52-
Before using sentire, you must set your Sentry API token as an environment variable:
52+
Sentire supports two methods for providing your Sentry API token: environment variables and configuration files.
53+
54+
### Environment Variable (Recommended)
55+
56+
Set your Sentry API token as an environment variable:
5357

5458
```bash
5559
export SENTRY_API_TOKEN=your_sentry_api_token_here
5660
```
5761

62+
### Configuration File
63+
64+
Alternatively, you can create a configuration file at `~/.config/sentire/config.json`:
65+
66+
```json
67+
{
68+
"sentry_api_token": "your_sentry_api_token_here"
69+
}
70+
```
71+
72+
### Configuration Precedence
73+
74+
If both are provided, the environment variable takes precedence over the configuration file. This allows you to:
75+
76+
- Use a config file for your default token
77+
- Override it temporarily with an environment variable when needed
78+
5879
You can obtain an API token from your Sentry organization settings under "Auth Tokens".
5980

6081
## Usage
@@ -317,6 +338,6 @@ Licensed under the MIT License. See [LICENSE](LICENSE) for details.
317338
Future enhancements may include:
318339

319340
-**Multiple output formats** (JSON, table, text, markdown) - **COMPLETED**
320-
- Configuration file support
341+
- **Configuration file support** - **COMPLETED**
321342
- Additional Sentry API endpoints
322343
- Export functionality (CSV, JSON files)

internal/client/client.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,15 @@ import (
66
"io"
77
"net/http"
88
"net/url"
9-
"os"
9+
"sentire/internal/config"
1010
"strconv"
1111
"strings"
1212
"time"
1313
)
1414

1515
const (
16-
BaseURL = "https://sentry.io/api/0"
17-
UserAgent = "sentire/1.0.0"
18-
TokenEnvVar = "SENTRY_API_TOKEN"
16+
BaseURL = "https://sentry.io/api/0"
17+
UserAgent = "sentire/1.0.0"
1918
)
2019

2120
// Client represents the Sentry API client
@@ -51,17 +50,17 @@ type Response struct {
5150

5251
// NewClient creates a new Sentry API client
5352
func NewClient() (*Client, error) {
54-
token := os.Getenv(TokenEnvVar)
55-
if token == "" {
56-
return nil, fmt.Errorf("environment variable %s is required", TokenEnvVar)
53+
cfg, err := config.LoadConfig()
54+
if err != nil {
55+
return nil, err
5756
}
5857

5958
return &Client{
6059
BaseURL: BaseURL,
6160
HTTPClient: &http.Client{
6261
Timeout: 30 * time.Second,
6362
},
64-
Token: token,
63+
Token: cfg.SentryAPIToken,
6564
RateLimit: &RateLimiter{},
6665
}, nil
6766
}

internal/config/config.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package config
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
)
9+
10+
// Config holds the application configuration
11+
type Config struct {
12+
SentryAPIToken string `json:"sentry_api_token"`
13+
}
14+
15+
// LoadConfig loads configuration from environment variables or config file
16+
// Environment variables take precedence over config file values
17+
func LoadConfig() (*Config, error) {
18+
config := &Config{}
19+
20+
// First, try to load from environment variable
21+
if token := os.Getenv("SENTRY_API_TOKEN"); token != "" {
22+
config.SentryAPIToken = token
23+
return config, nil
24+
}
25+
26+
// If env var is not set, try to load from config file
27+
configPath, err := getConfigPath()
28+
if err != nil {
29+
return nil, fmt.Errorf("failed to determine config path: %w", err)
30+
}
31+
32+
if err := loadFromFile(configPath, config); err != nil {
33+
// If config file doesn't exist or has issues, return error about missing token
34+
return nil, fmt.Errorf("SENTRY_API_TOKEN environment variable is required (or configure ~/.config/sentire/config.json)")
35+
}
36+
37+
// Validate that we have a token
38+
if config.SentryAPIToken == "" {
39+
return nil, fmt.Errorf("SENTRY_API_TOKEN is required in config file")
40+
}
41+
42+
return config, nil
43+
}
44+
45+
// getConfigPath returns the path to the config file
46+
func getConfigPath() (string, error) {
47+
homeDir, err := os.UserHomeDir()
48+
if err != nil {
49+
return "", fmt.Errorf("failed to get user home directory: %w", err)
50+
}
51+
52+
return filepath.Join(homeDir, ".config", "sentire", "config.json"), nil
53+
}
54+
55+
// loadFromFile loads configuration from a JSON file
56+
func loadFromFile(path string, config *Config) error {
57+
file, err := os.Open(path)
58+
if err != nil {
59+
return fmt.Errorf("failed to open config file: %w", err)
60+
}
61+
defer file.Close()
62+
63+
decoder := json.NewDecoder(file)
64+
if err := decoder.Decode(config); err != nil {
65+
return fmt.Errorf("failed to parse config file: %w", err)
66+
}
67+
68+
return nil
69+
}
70+
71+
// SaveConfig saves the current configuration to the config file
72+
// This is mainly for future use when we add more configuration options
73+
func SaveConfig(config *Config) error {
74+
configPath, err := getConfigPath()
75+
if err != nil {
76+
return fmt.Errorf("failed to determine config path: %w", err)
77+
}
78+
79+
// Create directory if it doesn't exist
80+
configDir := filepath.Dir(configPath)
81+
if err := os.MkdirAll(configDir, 0755); err != nil {
82+
return fmt.Errorf("failed to create config directory: %w", err)
83+
}
84+
85+
file, err := os.Create(configPath)
86+
if err != nil {
87+
return fmt.Errorf("failed to create config file: %w", err)
88+
}
89+
defer file.Close()
90+
91+
encoder := json.NewEncoder(file)
92+
encoder.SetIndent("", " ")
93+
if err := encoder.Encode(config); err != nil {
94+
return fmt.Errorf("failed to write config file: %w", err)
95+
}
96+
97+
return nil
98+
}

0 commit comments

Comments
 (0)