Skip to content
Open
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
6 changes: 6 additions & 0 deletions cmd/shell-operator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ func main() {
applySliceFlags := app.BindFlags(cfg, rootCmd, startCmd)
startCmd.PreRunE = func(_ *cobra.Command, _ []string) error {
applySliceFlags()
// Sync the resolved debug socket path into the package-level CLI
// default so any debug-* sub-command launched in the same process
// (e.g. embedded test harnesses) sees the same value the operator
// will bind to.
debug.DefaultSocketPath = cfg.Debug.UnixSocket
return nil
}
rootCmd.AddCommand(startCmd)
Expand All @@ -68,6 +73,7 @@ func main() {
rootCmd.RunE = start(logger, cfg)
rootCmd.PreRunE = func(_ *cobra.Command, _ []string) error {
applySliceFlags()
debug.DefaultSocketPath = cfg.Debug.UnixSocket
return nil
}

Expand Down
36 changes: 17 additions & 19 deletions cmd/shell-operator/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"syscall"

"github.com/deckhouse/deckhouse/pkg/log"
"github.com/spf13/cobra"
Expand All @@ -15,41 +17,37 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.30.0"

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

const (
AppName = "shell-operator"
AppDescription = "Shell-operator is a tool for running event-driven scripts in a Kubernetes cluster"
)

// start returns the cobra RunE used by the "start" sub-command. It owns the
// process-wide concerns (signal handling, OpenTelemetry, log defaults) and
// delegates the actual lifecycle to shell-operator's Run method, which Starts,
// blocks until ctx is done, and synchronously Shuts down.
func start(logger *log.Logger, cfg *app.Config) func(cmd *cobra.Command, args []string) error {
return func(_ *cobra.Command, _ []string) error {
app.AppStartMessage = fmt.Sprintf("%s %s", app.AppName, app.Version)
ctx := context.Background()
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()

telemetryShutdown := registerTelemetry(ctx)
defer func() { _ = telemetryShutdown(context.Background()) }()

// Initialize metric names with the configured prefix.
metrics.InitMetrics(cfg.App.PrometheusMetricsPrefix)
fmt.Println(app.AppName, app.Version)

// Init logging and initialize a ShellOperator instance.
operator, err := shell_operator.Init(ctx, cfg, logger.Named("shell-operator"))
op, err := shell_operator.NewShellOperator(ctx, cfg, shell_operator.WithLogger(logger.Named("shell-operator")))
if err != nil {
return fmt.Errorf("init failed: %w", err)
return fmt.Errorf("build shell-operator: %w", err)
}

operator.Start()

// Block until OS signal.
utils_signal.WaitForProcessInterruption(func() {
operator.Shutdown()
_ = telemetryShutdown(ctx)
os.Exit(1)
})

return nil
// Run blocks until ctx is canceled (typically by SIGINT/SIGTERM) and
// performs a synchronous Shutdown before returning. No os.Exit on the
// graceful path; the caller (cobra) maps a nil return to exit 0.
return op.Run(ctx)
}
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ require (
github.com/onsi/ginkgo/v2 v2.27.5
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
go.uber.org/goleak v1.3.0
)

require (
Expand Down
8 changes: 5 additions & 3 deletions pkg/app/app.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package app

// AppName and AppDescription are read-only product identifiers, safe to use
// from anywhere. AppStartMessage was previously a mutable global rewritten at
// startup; library consumers now build their own banner from AppName/Version.
var (
AppName = "shell-operator"
AppDescription = "Run your custom cluster-wide scripts in reaction to Kubernetes events or on schedule."
AppStartMessage = "shell-operator"
AppName = "shell-operator"
AppDescription = "Run your custom cluster-wide scripts in reaction to Kubernetes events or on schedule."
)

var Version = "v1.2.0-dev"
46 changes: 8 additions & 38 deletions pkg/app/debug.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,9 @@
package app

import (
"github.com/spf13/cobra"
)

// DebugUnixSocket is the default path for the debug unix socket.
// It is used as the binding target for the --debug-unix-socket flag on debug
// sub-commands (queue, hook, etc.) that connect to a running operator.
// For the start command, cfg.Debug.UnixSocket is preferred; see flags.go.
var DebugUnixSocket = "/var/run/shell-operator/debug.socket"

// ApplyConfig copies values from cfg into package-level globals that pre-date
// the Config struct and therefore cannot be populated by caarlos0/env (today
// just DebugUnixSocket).
//
// Call it whenever cfg may have been built outside the CLI flow — most
// importantly when an outer program (e.g. addon-operator) assembles its own
// *Config and hands it to shell-operator without going through BindFlags.
// After this call, debug sub-commands that bind --debug-unix-socket against
// the global will see cfg.Debug.UnixSocket as their default.
// Package app intentionally no longer exposes the DebugUnixSocket global or
// ApplyConfig helper that used to live here. The source of truth for the
// debug socket path is now cfg.Debug.UnixSocket (see app_config.go); CLI
// commands that need to connect to a running operator bind their
// --debug-unix-socket flag against the helper in package debug instead.
//
// A nil cfg is a no-op so callers don't need to guard. ApplyConfig is also
// invoked from BindFlags and shell_operator.Init, so most users get the
// override for free; calling it again is safe and idempotent.
func ApplyConfig(cfg *Config) {
if cfg == nil {
return
}
DebugUnixSocket = cfg.Debug.UnixSocket
}

// DefineDebugUnixSocketFlag registers the --debug-unix-socket flag on cmd,
// binding it to the DebugUnixSocket global. Called by debug sub-commands that
// need to locate the operator's debug socket.
func DefineDebugUnixSocketFlag(cmd *cobra.Command) {
cmd.Flags().StringVar(&DebugUnixSocket, "debug-unix-socket", DebugUnixSocket, "A path to a unix socket for a debug endpoint.")
_ = cmd.Flags().MarkHidden("debug-unix-socket")
}
// This file is kept (empty) so external code that imports the package keeps
// compiling; new code should not add package-level mutable state here.
package app
27 changes: 0 additions & 27 deletions pkg/app/debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,3 @@ func TestDebugKeepTempFilesIsBool(t *testing.T) {
cfg.Debug.KeepTempFiles = false
assert.False(t, cfg.Debug.KeepTempFiles)
}

// TestApplyConfig_OverridesDebugUnixSocket guarantees that handing a *Config
// built by an outer program to ApplyConfig replaces the package-level
// DebugUnixSocket global, even when BindFlags is never called.
func TestApplyConfig_OverridesDebugUnixSocket(t *testing.T) {
prev := DebugUnixSocket
t.Cleanup(func() { DebugUnixSocket = prev })

cfg := NewConfig()
cfg.Debug.UnixSocket = "/run/outer-program/debug.socket"

ApplyConfig(cfg)

assert.Equal(t, "/run/outer-program/debug.socket", DebugUnixSocket)
}

// TestApplyConfig_NilIsNoop documents that ApplyConfig tolerates a nil cfg
// so callers don't need to guard at every call site.
func TestApplyConfig_NilIsNoop(t *testing.T) {
prev := DebugUnixSocket
t.Cleanup(func() { DebugUnixSocket = prev })

DebugUnixSocket = "/run/sentinel.socket"
ApplyConfig(nil)

assert.Equal(t, "/run/sentinel.socket", DebugUnixSocket)
}
7 changes: 3 additions & 4 deletions pkg/app/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,9 @@ func bindLogFlags(cfg *Config, cmd *cobra.Command) {
}

func bindDebugFlags(cfg *Config, rootCmd *cobra.Command, cmd *cobra.Command) {
// Sync the package-level global so debug sub-commands (queue, hook, etc.)
// that bind to DebugUnixSocket get the env/default value.
ApplyConfig(cfg)

// Note: cfg.Debug.UnixSocket is the source of truth. The CLI (main.go)
// is responsible for syncing it into debug.DefaultSocketPath after flag
// parsing so debug sub-commands can connect to the same socket.
f := cmd.Flags()
f.StringVar(&cfg.Debug.UnixSocket, "debug-unix-socket", cfg.Debug.UnixSocket, "A path to a unix socket for a debug endpoint. Can be set with $DEBUG_UNIX_SOCKET.")
_ = f.MarkHidden("debug-unix-socket")
Expand Down
32 changes: 32 additions & 0 deletions pkg/debug/cli_socket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright 2025 Flant JSC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package debug

import (
"github.com/spf13/cobra"
)

// DefaultSocketPath is the path the debug CLI client (DefaultClient) connects
// to when no explicit socket has been configured. It is a CLI concern only —
// library consumers should build a debug.Client with NewClient(path) directly.
var DefaultSocketPath = "/var/run/shell-operator/debug.socket"

// DefineSocketFlag binds the --debug-unix-socket flag on cmd to the
// process-level DefaultSocketPath used by DefaultClient(). Called by debug
// sub-commands (queue, hook, config, raw).
func DefineSocketFlag(cmd *cobra.Command) {
cmd.Flags().StringVar(&DefaultSocketPath, "debug-unix-socket", DefaultSocketPath, "A path to a unix socket for a debug endpoint.")
_ = cmd.Flags().MarkHidden("debug-unix-socket")
}
6 changes: 4 additions & 2 deletions pkg/debug/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"net/http"
"time"

"github.com/flant/shell-operator/pkg/app"
utils "github.com/flant/shell-operator/pkg/utils/file"
)

Expand Down Expand Up @@ -53,8 +52,11 @@ func (c *Client) Close() {
}
}

// DefaultClient connects to the unix socket pointed at by DefaultSocketPath.
// CLI commands bind --debug-unix-socket against DefaultSocketPath via
// DefineSocketFlag so that this function picks up any flag-supplied override.
func DefaultClient() (*Client, error) {
return NewClient(app.DebugUnixSocket)
return NewClient(DefaultSocketPath)
}

func (c *Client) Get(url string) ([]byte, error) {
Expand Down
16 changes: 7 additions & 9 deletions pkg/debug/debug-cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (

"github.com/muesli/termenv"
"github.com/spf13/cobra"

"github.com/flant/shell-operator/pkg/app"
)

var (
Expand Down Expand Up @@ -86,7 +84,7 @@ func DefineDebugCommands(rootCmd *cobra.Command) {
queueListCmd.Flags().BoolVarP(&watch, "watch", "w", false, "Keep watching.")
queueListCmd.Flags().StringVarP(&watchInterval, "watchInterval", "t", watchInterval, "Watch refresh interval.")
AddOutputJsonYamlTextFlag(queueListCmd)
app.DefineDebugUnixSocketFlag(queueListCmd)
DefineSocketFlag(queueListCmd)
queueCmd.AddCommand(queueListCmd)

queueMainCmd := &cobra.Command{
Expand All @@ -106,7 +104,7 @@ func DefineDebugCommands(rootCmd *cobra.Command) {
},
}
AddOutputJsonYamlTextFlag(queueMainCmd)
app.DefineDebugUnixSocketFlag(queueMainCmd)
DefineSocketFlag(queueMainCmd)
queueCmd.AddCommand(queueMainCmd)

// Runtime config command.
Expand All @@ -130,7 +128,7 @@ func DefineDebugCommands(rootCmd *cobra.Command) {
},
}
AddOutputJsonYamlTextFlag(configListCmd)
app.DefineDebugUnixSocketFlag(configListCmd)
DefineSocketFlag(configListCmd)
configCmd.AddCommand(configListCmd)

var paramName string
Expand Down Expand Up @@ -162,7 +160,7 @@ func DefineDebugCommands(rootCmd *cobra.Command) {
return nil
},
}
app.DefineDebugUnixSocketFlag(configSetCmd)
DefineSocketFlag(configSetCmd)
configCmd.AddCommand(configSetCmd)

// Raw request command
Expand All @@ -188,7 +186,7 @@ func DefineDebugCommands(rootCmd *cobra.Command) {
return nil
},
}
app.DefineDebugUnixSocketFlag(rawCommand)
DefineSocketFlag(rawCommand)
rootCmd.AddCommand(rawCommand)
}

Expand All @@ -213,7 +211,7 @@ func DefineDebugCommandsSelf(rootCmd *cobra.Command) {
},
}
AddOutputJsonYamlTextFlag(hookListCmd)
app.DefineDebugUnixSocketFlag(hookListCmd)
DefineSocketFlag(hookListCmd)
hookCmd.AddCommand(hookListCmd)

hookSnapshotCmd := &cobra.Command{
Expand All @@ -234,7 +232,7 @@ func DefineDebugCommandsSelf(rootCmd *cobra.Command) {
},
}
AddOutputJsonYamlTextFlag(hookSnapshotCmd)
app.DefineDebugUnixSocketFlag(hookSnapshotCmd)
DefineSocketFlag(hookSnapshotCmd)
hookCmd.AddCommand(hookSnapshotCmd)
}

Expand Down
Loading
Loading