Skip to content
3 changes: 2 additions & 1 deletion cmd/shell-operator/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/flant/shell-operator/pkg/app"
shell_operator "github.com/flant/shell-operator/pkg/shell-operator"
operatorconfig "github.com/flant/shell-operator/pkg/shell-operator/config"
utils_signal "github.com/flant/shell-operator/pkg/utils/signal"
)

Expand All @@ -30,7 +31,7 @@ func start(logger *log.Logger) func(_ *kingpin.ParseContext) error {
ctx := context.Background()
telemetryShutdown := registerTelemetry(ctx)
// Init logging and initialize a ShellOperator instance.
operator, err := shell_operator.Init(logger.Named("shell-operator"))
operator, err := shell_operator.NewShellOperatorWithOptions(context.TODO(), operatorconfig.WithLogger(logger))
if err != nil {
return fmt.Errorf("init failed: %w", err)
}
Expand Down
166 changes: 166 additions & 0 deletions examples/API_USAGE_EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Shell Operator API Usage Examples

## Complete API Reference

The Shell Operator now provides a flexible, options-based API for initialization. Here are all the available ways to create a ShellOperator instance:

### 1. Basic Constructor (Original)
```go
import (
"github.com/flant/shell-operator/pkg/shell-operator"
"log"
)

// Simple constructor - uses default logger and no metrics storage
operator := shell_operator.NewShellOperator()
```

### 2. Configuration-Based Constructor
```go
import (
"context"
"github.com/flant/shell-operator/pkg/shell-operator"
"github.com/flant/shell-operator/pkg/shell-operator/config"
)

// Using the flexible configuration system
operator, err := shell_operator.NewShellOperatorWithOptions(ctx,
config.WithLogger(logger),
config.WithMetricStorage(storage),
config.WithHookMetricStorage(hookStorage),
config.WithHooksDir("/custom/hooks"),
)
```

### 3. With Separate Metric Storages
```go
import (
"github.com/flant/shell-operator/pkg/shell-operator"
metricsstorage "github.com/deckhouse/deckhouse/pkg/metrics-storage"
)

builtinStorage := metricsstorage.NewStorage("builtin-metrics")
hookStorage := metricsstorage.NewStorage("hook-metrics")

operator, err := shell_operator.NewShellOperatorWithOptions(ctx,
shell_operator.WithMetricStorage(builtinStorage),
shell_operator.WithHookMetricStorage(hookStorage),
)
```

### 4. With Convenience Function for Both Storages
```go
import (
"github.com/flant/shell-operator/pkg/shell-operator"
metricsstorage "github.com/deckhouse/deckhouse/pkg/metrics-storage"
)

storage := metricsstorage.NewStorage("my-app")

// Set both metric storages at once
operator, err := shell_operator.NewShellOperatorWithOptions(ctx,
config.WithMetricStorages(storage, storage), // Same storage for both
)
```

### 5. Convenience Constructor with Logger
```go
// When you primarily need to provide a logger
operator, err := shell_operator.NewShellOperatorWithLogger(ctx, logger,
config.WithMetricStorage(storage),
)
```

### 6. Configuration Presets
```go
// Development configuration with sensible defaults
cfg := shell_operator.NewDevelopmentConfig()
operator, err := shell_operator.NewShellOperatorWithConfig(ctx, cfg)

// Production configuration
cfg := shell_operator.NewProductionConfig()
operator, err := shell_operator.NewShellOperatorWithConfig(ctx, cfg)
```

### 7. Advanced Configuration
```go
// Full control over configuration
cfg := config.NewShellOperatorConfig(
config.WithLogger(customLogger),
config.WithMetricStorage(metricsStorage),
config.WithHookMetricStorage(hookMetricsStorage),
config.WithListenAddress("0.0.0.0"),
config.WithListenPort("9090"),
config.WithHooksDir("/app/hooks"),
config.WithTempDir("/tmp/shell-operator"),
)

operator, err := shell_operator.NewShellOperatorWithConfig(ctx, cfg)
```

## Available Configuration Options

### Configuration Options (for NewShellOperatorWithOptions)
All configuration options are available in the `config` package:
- `config.WithLogger(logger *log.Logger)` - Set the logger
- `config.WithMetricStorage(storage)` - Set the main metrics storage
- `config.WithHookMetricStorage(storage)` - Set the hook-specific metrics storage
- `config.WithMetricStorages(metricStorage, hookStorage)` - Set both metric storages at once
- `config.WithListenAddress(address string)` - HTTP server listen address
- `config.WithListenPort(port string)` - HTTP server listen port
- `config.WithHooksDir(dir string)` - Directory containing hooks
- `config.WithTempDir(dir string)` - Temporary directory
- `config.WithDebugUnixSocket(socket string)` - Debug unix socket path
- `config.WithDebugHttpServerAddr(addr string)` - Debug HTTP server address

### Convenience Options
- `config.WithListenConfig(address, port string)` - Set both listen address and port
- `config.WithDirectories(hooksDir, tempDir string)` - Set both hooks and temp directories
- `config.WithDebugConfig(unixSocket, httpAddr string)` - Set both debug configurations
- `config.WithMetricStorages(metricStorage, hookStorage)` - Set both metric storages

## Migration from Old API

### Before (Old Init function)
```go
// Old way - rigid initialization
operator, err := shell_operator.Init(logger, metricsStorage)
```

### After (New Options Pattern)
```go
// New way - flexible configuration options
operator, err := shell_operator.NewShellOperatorWithOptions(ctx,
config.WithLogger(logger),
config.WithMetricStorage(metricsStorage),
)

// Or using convenience function
operator, err := shell_operator.NewShellOperatorWithLogger(ctx, logger,
config.WithMetricStorage(metricsStorage),
)
```

## Error Handling

All constructor functions return an error if configuration validation fails:

```go
operator, err := shell_operator.NewShellOperatorWithOptions(ctx,
shell_operator.WithHooksDir(""), // Invalid: empty hooks directory
)
if err != nil {
log.Fatalf("Failed to create operator: %v", err)
}
```

## Best Practices

1. **Use separate metric storage options**: `WithMetricStorage()` and `WithHookMetricStorage()` for explicit control
2. **Use convenience functions when appropriate**: `WithMetricStorages()` when both storages are the same
3. **Use configuration options for complex setups**: When you need multiple configuration parameters
4. **Use presets for common scenarios**: Development vs Production configurations
5. **Always handle errors**: Constructor functions validate configuration and can fail
6. **Prefer explicit options**: Makes configuration clear and maintainable

This API provides backward compatibility while enabling flexible, maintainable configuration patterns.
7 changes: 0 additions & 7 deletions internal/metrics/metrics.go

This file was deleted.

3 changes: 2 additions & 1 deletion pkg/kube_events_manager/error_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/tools/cache"

"github.com/flant/shell-operator/pkg/metrics"
utils "github.com/flant/shell-operator/pkg/utils/labels"
)

Expand Down Expand Up @@ -63,7 +64,7 @@ func (weh *WatchErrorHandler) handler(_ *cache.Reflector, err error) {
}

if weh.metricStorage != nil {
weh.metricStorage.CounterAdd("{PREFIX}kubernetes_client_watch_errors_total", 1.0, map[string]string{"error_type": errorType})
weh.metricStorage.CounterAdd(metrics.KubernetesClientWatchErrorsTotal, 1.0, map[string]string{"error_type": errorType})
}
}

Expand Down
15 changes: 8 additions & 7 deletions pkg/kube_events_manager/monitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/flant/kube-client/manifest"
kemtypes "github.com/flant/shell-operator/pkg/kube_events_manager/types"
"github.com/flant/shell-operator/pkg/metric"
"github.com/flant/shell-operator/pkg/metrics"
)

func Test_Monitor_should_handle_dynamic_ns_events(t *testing.T) {
Expand Down Expand Up @@ -43,18 +44,18 @@ func Test_Monitor_should_handle_dynamic_ns_events(t *testing.T) {

metricStorage := metric.NewStorageMock(t)
metricStorage.HistogramObserveMock.Set(func(metric string, value float64, labels map[string]string, buckets []float64) {
metrics := []string{
"{PREFIX}kube_event_duration_seconds",
"{PREFIX}kube_jq_filter_duration_seconds",
metricsList := []string{
metrics.KubeEventDurationSeconds,
metrics.KubeJqFilterDurationSeconds,
}
assert.Contains(t, metrics, metric)
assert.Contains(t, metricsList, metric)
assert.NotZero(t, value)
assert.Equal(t, map[string]string(nil), labels)
assert.Nil(t, buckets)
})
metricStorage.GaugeSetMock.When("{PREFIX}kube_snapshot_objects", 1, map[string]string(nil)).Then()
metricStorage.GaugeSetMock.When("{PREFIX}kube_snapshot_objects", 2, map[string]string(nil)).Then()
metricStorage.GaugeSetMock.When("{PREFIX}kube_snapshot_objects", 3, map[string]string(nil)).Then()
metricStorage.GaugeSetMock.When(metrics.KubeSnapshotObjects, 1, map[string]string(nil)).Then()
metricStorage.GaugeSetMock.When(metrics.KubeSnapshotObjects, 2, map[string]string(nil)).Then()
metricStorage.GaugeSetMock.When(metrics.KubeSnapshotObjects, 3, map[string]string(nil)).Then()

mon := NewMonitor(context.Background(), fc.Client, metricStorage, monitorCfg, func(ev kemtypes.KubeEvent) {
objsMutex.Lock()
Expand Down
13 changes: 7 additions & 6 deletions pkg/kube_events_manager/resource_informer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
klient "github.com/flant/kube-client/client"
"github.com/flant/shell-operator/pkg/filter/jq"
kemtypes "github.com/flant/shell-operator/pkg/kube_events_manager/types"
"github.com/flant/shell-operator/pkg/metrics"
"github.com/flant/shell-operator/pkg/utils/measure"
)

Expand Down Expand Up @@ -224,7 +225,7 @@ func (ei *resourceInformer) loadExistedObjects() error {
var err error
func() {
defer measure.Duration(func(d time.Duration) {
ei.metricStorage.HistogramObserve("{PREFIX}kube_jq_filter_duration_seconds", d.Seconds(), ei.Monitor.Metadata.MetricLabels, nil)
ei.metricStorage.HistogramObserve(metrics.KubeJqFilterDurationSeconds, d.Seconds(), ei.Monitor.Metadata.MetricLabels, nil)
})()
filter := jq.NewFilter()
objFilterRes, err = applyFilter(ei.Monitor.JqFilter, filter, ei.Monitor.FilterFunc, &obj)
Expand Down Expand Up @@ -254,7 +255,7 @@ func (ei *resourceInformer) loadExistedObjects() error {
}

ei.cachedObjectsInfo.Count = uint64(len(ei.cachedObjects))
ei.metricStorage.GaugeSet("{PREFIX}kube_snapshot_objects", float64(len(ei.cachedObjects)), ei.Monitor.Metadata.MetricLabels)
ei.metricStorage.GaugeSet(metrics.KubeSnapshotObjects, float64(len(ei.cachedObjects)), ei.Monitor.Metadata.MetricLabels)

return nil
}
Expand Down Expand Up @@ -287,7 +288,7 @@ func (ei *resourceInformer) handleWatchEvent(object interface{}, eventType kemty
}

defer measure.Duration(func(d time.Duration) {
ei.metricStorage.HistogramObserve("{PREFIX}kube_event_duration_seconds", d.Seconds(), ei.Monitor.Metadata.MetricLabels, nil)
ei.metricStorage.HistogramObserve(metrics.KubeEventDurationSeconds, d.Seconds(), ei.Monitor.Metadata.MetricLabels, nil)
})()
defer trace.StartRegion(context.Background(), "handleWatchEvent").End()

Expand All @@ -304,7 +305,7 @@ func (ei *resourceInformer) handleWatchEvent(object interface{}, eventType kemty
var err error
func() {
defer measure.Duration(func(d time.Duration) {
ei.metricStorage.HistogramObserve("{PREFIX}kube_jq_filter_duration_seconds", d.Seconds(), ei.Monitor.Metadata.MetricLabels, nil)
ei.metricStorage.HistogramObserve(metrics.KubeJqFilterDurationSeconds, d.Seconds(), ei.Monitor.Metadata.MetricLabels, nil)
})()
filter := jq.NewFilter()
objFilterRes, err = applyFilter(ei.Monitor.JqFilter, filter, ei.Monitor.FilterFunc, obj)
Expand Down Expand Up @@ -351,7 +352,7 @@ func (ei *resourceInformer) handleWatchEvent(object interface{}, eventType kemty
ei.cachedObjectsIncrement.Modified++
}
// Update metrics.
ei.metricStorage.GaugeSet("{PREFIX}kube_snapshot_objects", float64(len(ei.cachedObjects)), ei.Monitor.Metadata.MetricLabels)
ei.metricStorage.GaugeSet(metrics.KubeSnapshotObjects, float64(len(ei.cachedObjects)), ei.Monitor.Metadata.MetricLabels)
ei.cacheLock.Unlock()
if skipEvent {
return
Expand All @@ -369,7 +370,7 @@ func (ei *resourceInformer) handleWatchEvent(object interface{}, eventType kemty
ei.cachedObjectsInfo.Deleted++
ei.cachedObjectsIncrement.Deleted++
// Update metrics.
ei.metricStorage.GaugeSet("{PREFIX}kube_snapshot_objects", float64(len(ei.cachedObjects)), ei.Monitor.Metadata.MetricLabels)
ei.metricStorage.GaugeSet(metrics.KubeSnapshotObjects, float64(len(ei.cachedObjects)), ei.Monitor.Metadata.MetricLabels)
ei.cacheLock.Unlock()
}

Expand Down
Loading
Loading