diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 200a0ae7..2f7afa90 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -8,7 +8,7 @@ on: jobs: deploy: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 concurrency: group: ${{ github.workflow }}-${{ github.ref }} steps: diff --git a/.gitignore b/.gitignore index b7fcc4ff..6b60f726 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ .idea *.iml *.proj +.vscode # C Dependency libjq diff --git a/cmd/shell-operator/start.go b/cmd/shell-operator/start.go index ede26149..a4aedb5e 100644 --- a/cmd/shell-operator/start.go +++ b/cmd/shell-operator/start.go @@ -25,27 +25,27 @@ const ( ) func start(logger *log.Logger) func(_ *kingpin.ParseContext) error { - app.AppStartMessage = fmt.Sprintf("%s %s", app.AppName, app.Version) - ctx := context.Background() - telemetryShutdown := registerTelemetry(ctx) - // Init logging and initialize a ShellOperator instance. - operator, err := shell_operator.Init(logger.Named("shell-operator")) - if err != nil { - return func(_ *kingpin.ParseContext) error { + return func(_ *kingpin.ParseContext) error { + app.AppStartMessage = fmt.Sprintf("%s %s", app.AppName, app.Version) + ctx := context.Background() + telemetryShutdown := registerTelemetry(ctx) + // Init logging and initialize a ShellOperator instance. + operator, err := shell_operator.Init(logger.Named("shell-operator")) + if err != nil { return fmt.Errorf("init failed: %w", err) } - } - operator.Start() + operator.Start() - // Block action by waiting signals from OS. - utils_signal.WaitForProcessInterruption(func() { - operator.Shutdown() - _ = telemetryShutdown(ctx) - os.Exit(1) - }) + // Block action by waiting signals from OS. + utils_signal.WaitForProcessInterruption(func() { + operator.Shutdown() + _ = telemetryShutdown(ctx) + os.Exit(1) + }) - return nil + return nil + } } func registerTelemetry(ctx context.Context) func(ctx context.Context) error { diff --git a/pkg/debug/server.go b/pkg/debug/server.go index 7254f19f..f62bca58 100644 --- a/pkg/debug/server.go +++ b/pkg/debug/server.go @@ -9,6 +9,8 @@ import ( "net/http" "os" "path" + "path/filepath" + "strings" "github.com/deckhouse/deckhouse/pkg/log" "github.com/go-chi/chi/v5" @@ -117,12 +119,15 @@ func (s *Server) RegisterHandler(method, pattern string, handler func(request *h func handleFormattedOutput(writer http.ResponseWriter, request *http.Request, handler func(request *http.Request) (interface{}, error)) { out, err := handler(request) if err != nil { - if _, ok := err.(*BadRequestError); ok { + switch err.(type) { + case *BadRequestError: http.Error(writer, err.Error(), http.StatusBadRequest) - return + case *NotFoundError: + http.Error(writer, err.Error(), http.StatusNotFound) + default: + http.Error(writer, err.Error(), http.StatusInternalServerError) } - http.Error(writer, err.Error(), http.StatusInternalServerError) return } if out == nil { @@ -130,7 +135,16 @@ func handleFormattedOutput(writer http.ResponseWriter, request *http.Request, ha return } - format := FormatFromRequest(request) + // Trying to get format from chi + format := chi.URLParam(request, "format") + if format == "" { // If failed, trying to parse uri + uri := request.URL.Path + uriFragments := strings.Split(uri, "/") + uriLastFragment := uriFragments[len(uriFragments)-1] // string after last "/" to ignore garbage + format = filepath.Ext(uriLastFragment) // Extracts extension of path (like .yaml), may return empty string + format = strings.TrimPrefix(format, ".") + } + structuredLogger.GetLogEntry(request).Debug("used format", slog.String("format", format)) switch format { @@ -140,6 +154,10 @@ func handleFormattedOutput(writer http.ResponseWriter, request *http.Request, ha writer.Header().Set("Content-Type", "application/json") case "yaml": writer.Header().Set("Content-Type", "application/yaml") + // support for old behavior. If the extension is not indicated, we use text by default + case "": + format = "text" + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") } writer.WriteHeader(http.StatusOK) @@ -196,3 +214,11 @@ type BadRequestError struct { func (be *BadRequestError) Error() string { return be.Msg } + +type NotFoundError struct { + Msg string +} + +func (nf *NotFoundError) Error() string { + return nf.Msg +} diff --git a/pkg/filter/jq/apply_benchmark_test.go b/pkg/filter/jq/apply_benchmark_test.go new file mode 100644 index 00000000..0897054c --- /dev/null +++ b/pkg/filter/jq/apply_benchmark_test.go @@ -0,0 +1,57 @@ +package jq + +import ( + "os" + "testing" + + "gopkg.in/yaml.v3" +) + +func setupNodeList(count int) []map[string]any { + var node map[string]any + content, _ := os.ReadFile("testdata/test.yaml") + yaml.Unmarshal(content, &node) + + nodeslice := make([]map[string]any, 0, count) + + for range count { + nodeslice = append(nodeslice, node) + } + + return nodeslice +} + +func benchmarkApplyFilter(b *testing.B, filter string, objs []map[string]any) { + f := NewFilter() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for _, obj := range objs { + // res, _ := f.ApplyFilter(filter, obj) + // fmt.Println(string(res)) + f.ApplyFilter(filter, obj) + } + } +} + +func BenchmarkApplyFilter_dot_10(b *testing.B) { + benchmarkApplyFilter(b, ".", setupNodeList(10)) +} +func BenchmarkApplyFilter_dot_100(b *testing.B) { + benchmarkApplyFilter(b, ".", setupNodeList(100)) +} +func BenchmarkApplyFilter_dot_1000(b *testing.B) { + benchmarkApplyFilter(b, ".", setupNodeList(1000)) +} + +func BenchmarkApplyFilter_easy_10(b *testing.B) { + benchmarkApplyFilter(b, ".metadata.name", setupNodeList(10)) +} + +func BenchmarkApplyFilter_easy_100(b *testing.B) { + benchmarkApplyFilter(b, ".metadata.name", setupNodeList(100)) +} + +func BenchmarkApplyFilter_easy_1000(b *testing.B) { + benchmarkApplyFilter(b, ".metadata.name", setupNodeList(1000)) +} diff --git a/pkg/shell-operator/debug_server.go b/pkg/shell-operator/debug_server.go index 0bbef5d4..3b406d82 100644 --- a/pkg/shell-operator/debug_server.go +++ b/pkg/shell-operator/debug_server.go @@ -3,17 +3,20 @@ package shell_operator import ( "fmt" "net/http" + "regexp" "strconv" "time" "github.com/deckhouse/deckhouse/pkg/log" - "github.com/go-chi/chi/v5" "github.com/flant/shell-operator/pkg/config" "github.com/flant/shell-operator/pkg/debug" "github.com/flant/shell-operator/pkg/task/dump" ) +// hook path may be nested like: /hook/myfolder/myhook.sh/snapshots +var snapshotRe = regexp.MustCompile(`/hook/(.*)/snapshots.*`) + // RunDefaultDebugServer initialized and run default debug server on unix and http sockets // This method is also used in addon-operator func RunDefaultDebugServer(unixSocket, httpServerAddress string, logger *log.Logger) (*debug.Server, error) { @@ -53,9 +56,30 @@ func (op *ShellOperator) RegisterDebugHookRoutes(dbgSrv *debug.Server) { return op.HookManager.GetHookNames(), nil }) - dbgSrv.RegisterHandler(http.MethodGet, "/hook/{name}/snapshots.{format:(json|yaml|text)}", func(r *http.Request) (interface{}, error) { - hookName := chi.URLParam(r, "name") + // handler for dump hook snapshots + // Example path: /hook/100-test.sh/snapshots.text + dbgSrv.RegisterHandler(http.MethodGet, "/hook/*", func(r *http.Request) (interface{}, error) { + // check regex match + isMatched := snapshotRe.MatchString(r.RequestURI) + if !isMatched { + return nil, &debug.NotFoundError{Msg: "404 page not found"} + } + + // Extracting hook name from URI + matched := snapshotRe.FindStringSubmatch(r.RequestURI) // expression returns slice of: matched substring, matched group hookName + var hookName string + if len(matched) >= 2 { // expected presence of second element (hookName) + hookName = matched[1] + } + if hookName == "" { + return nil, &debug.BadRequestError{Msg: "'hook' parameter is required"} + } + + // Return hook snapshot dump h := op.HookManager.GetHook(hookName) + if h == nil { + return nil, &debug.BadRequestError{Msg: fmt.Sprintf("hook '%s' is not exist", hookName)} + } return h.HookController.SnapshotsDump(), nil }) } diff --git a/pkg/shell-operator/operator.go b/pkg/shell-operator/operator.go index 5c627a49..0de57576 100644 --- a/pkg/shell-operator/operator.go +++ b/pkg/shell-operator/operator.go @@ -96,11 +96,14 @@ func NewShellOperator(ctx context.Context, opts ...Option) *ShellOperator { func (op *ShellOperator) Start() { log.Info("start shell-operator") + op.logger.Debug("start APIServer") op.APIServer.Start(op.ctx) // Create 'main' queue and add onStartup tasks and enable bindings tasks. + op.logger.Debug("start bootstrapMainQueue") op.bootstrapMainQueue(op.TaskQueues) // Start main task queue handler + op.logger.Debug("start TaskQueues StartMain") op.TaskQueues.StartMain(op.ctx) op.initAndStartHookQueues() @@ -275,6 +278,7 @@ func (op *ShellOperator) initValidatingWebhookManager() error { return admissionResponse, nil }) + op.logger.Debug("debug ValidatingWebhookManager ValidatingResources", slog.Any("ValidatingResources", op.AdmissionWebhookManager.ValidatingResources)) if err := op.AdmissionWebhookManager.Start(); err != nil { return fmt.Errorf("ValidatingWebhookManager start: %w", err) } diff --git a/pkg/webhook/admission/resource.go b/pkg/webhook/admission/resource.go index b2299a16..5a726ecb 100644 --- a/pkg/webhook/admission/resource.go +++ b/pkg/webhook/admission/resource.go @@ -2,6 +2,7 @@ package admission import ( "context" + "fmt" "log/slog" "strings" @@ -63,9 +64,13 @@ func (w *ValidatingWebhookResource) Register() error { slog.String("path", *webhook.ClientConfig.Service.Path), slog.String("configurationName", w.opts.ConfigurationName)) + log.Debug("debug w.opts.ServiceName", slog.String("w.opts.ServiceName", w.opts.ServiceName)) + log.Debug("debug ValidatingWebhook client", slog.String("webhook.ValidatingWebhook.ClientConfig.Service.Name", webhook.ValidatingWebhook.ClientConfig.Service.Name)) + configuration.Webhooks = append(configuration.Webhooks, *webhook.ValidatingWebhook) } + fmt.Println(configuration.Webhooks) return w.submit(configuration) }