blob: 963d7db1e94a70cf072d8e481f5a03250912e460 [file]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you 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 preflight performs admin-server feature detection by reading the effective
// configuration dump, mirroring how Horizon UI degrades gracefully when an admin
// feature module is disabled or the admin host is unreachable.
package preflight
import (
"context"
"errors"
"fmt"
"net/http"
"strings"
"github.com/apache/skywalking-cli/pkg/admin/client"
)
// Known admin feature-module selector keys (the first dotted segment of a config-dump
// key) and the environment variables that enable them.
const (
ModuleAdminServer = "admin-server"
ModuleStatus = "status"
ModuleInspect = "inspect"
ModuleUIManage = "ui-management"
ModuleDSLDebug = "dsl-debugging"
ModuleRuntimeRule = "receiver-runtime-rule"
)
// Module describes one admin feature module and how to enable it.
type Module struct {
Name string `json:"name"`
EnvVar string `json:"envVar"`
Enabled bool `json:"enabled"`
}
// Result is the outcome of a preflight check against the admin host.
type Result struct {
AdminURL string `json:"adminURL"`
AdminReachable bool `json:"adminReachable"`
Modules []Module `json:"modules"`
enabled map[string]bool
}
// IsEnabled reports whether a feature module (by selector key) is enabled.
func (r *Result) IsEnabled(module string) bool { return r.enabled[module] }
var knownModules = []struct{ name, env string }{
{ModuleAdminServer, "SW_ADMIN_SERVER"},
{ModuleStatus, "SW_STATUS"},
{ModuleInspect, "SW_INSPECT"},
{ModuleUIManage, "SW_UI_MANAGEMENT"},
{ModuleDSLDebug, "SW_DSL_DEBUGGING"},
{ModuleRuntimeRule, "SW_RECEIVER_RUNTIME_RULE"},
}
// Run reads GET /debugging/config/dump from the admin host and reports which feature
// modules are enabled. A module is considered enabled when any dotted config key
// starts with `<module>.`. When the dump cannot be fetched, AdminReachable is false,
// every module reports disabled, and the transport error is returned alongside the
// (still useful) Result so callers can surface the admin URL.
func Run(ctx context.Context) (*Result, error) {
r := &Result{AdminURL: client.BaseURL(ctx), enabled: map[string]bool{}}
var dump map[string]any
err := client.GetJSON(ctx, "/debugging/config/dump", nil, &dump)
if err == nil {
r.AdminReachable = true
for k := range dump {
prefix := k
if i := strings.IndexByte(k, '.'); i >= 0 {
prefix = k[:i]
}
r.enabled[prefix] = true
}
}
for _, m := range knownModules {
r.Modules = append(r.Modules, Module{Name: m.name, EnvVar: m.env, Enabled: r.enabled[m.name]})
}
return r, err
}
// Explain enriches an admin-call error with operator-actionable context. A transport
// failure is reported as an unreachable admin host. A 404 with no recognizable JSON
// error envelope is reported as a likely-disabled module (the route is not registered),
// whereas a 404 that carries an error body (e.g. {"error":"not_found"}) is a real
// resource miss from an enabled module and is returned unchanged — as are all other API
// errors (400/409/421/...), which are already specific.
func Explain(ctx context.Context, err error, module, envVar string) error {
if err == nil {
return nil
}
adminURL := client.BaseURL(ctx)
var apiErr *client.APIError
if errors.As(err, &apiErr) {
if apiErr.StatusCode == http.StatusNotFound && apiErr.Message == "" && apiErr.Status == "" {
return fmt.Errorf("the `%s` admin feature module appears disabled on OAP "+
"(HTTP 404 with no error body at %s); enable it with %s=default. original error: %w",
module, adminURL, envVar, err)
}
return err
}
return fmt.Errorf("could not reach the OAP admin-server at %s; "+
"verify --admin-url and that admin-server is enabled (SW_ADMIN_SERVER=default). "+
"original error: %w", adminURL, err)
}