1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
package kasapp
import (
"context"
"fmt"
"os"
"github.com/go-logr/zapr"
"github.com/spf13/cobra"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/errz"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/logz"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/internal/tool/metric"
"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v16/pkg/kascfg"
"go.opentelemetry.io/otel"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"go.uber.org/zap/zapgrpc"
"google.golang.org/grpc/grpclog"
"google.golang.org/protobuf/encoding/protojson"
"k8s.io/klog/v2"
"sigs.k8s.io/yaml"
)
const (
envVarOwnPrivateApiUrl = "OWN_PRIVATE_API_URL"
envVarOwnPrivateApiHost = "OWN_PRIVATE_API_HOST"
)
type App struct {
ConfigurationFile string
OwnPrivateApiUrl string
OwnPrivateApiHost string
}
func (a *App) Run(ctx context.Context) (retErr error) {
cfg, err := LoadConfigurationFile(a.ConfigurationFile)
if err != nil {
return err
}
ApplyDefaultsToKasConfigurationFile(cfg)
err = cfg.ValidateExtra()
if err != nil {
return fmt.Errorf("kascfg.ValidateExtra: %w", err)
}
log, grpcLog, err := loggerFromConfig(cfg.Observability.Logging)
if err != nil {
return err
}
defer errz.SafeCall(log.Sync, &retErr)
defer errz.SafeCall(grpcLog.Sync, &retErr)
grpclog.SetLoggerV2(zapgrpc.NewLogger(grpcLog)) // pipe gRPC logs into zap
logrLogger := zapr.NewLogger(log)
// Kubernetes uses klog so here we pipe all logs from it to our logger via an adapter.
klog.SetLogger(logrLogger)
otel.SetLogger(logrLogger)
otel.SetErrorHandler((*metric.OtelErrorHandler)(log))
app := ConfiguredApp{
Log: log,
Configuration: cfg,
OwnPrivateApiUrl: a.OwnPrivateApiUrl,
OwnPrivateApiHost: a.OwnPrivateApiHost,
}
return app.Run(ctx)
}
func LoadConfigurationFile(configFile string) (*kascfg.ConfigurationFile, error) {
configYAML, err := os.ReadFile(configFile) // nolint: gosec
if err != nil {
return nil, fmt.Errorf("configuration file: %w", err)
}
configJSON, err := yaml.YAMLToJSON(configYAML)
if err != nil {
return nil, fmt.Errorf("YAMLToJSON: %w", err)
}
cfg := &kascfg.ConfigurationFile{}
err = protojson.Unmarshal(configJSON, cfg)
if err != nil {
return nil, fmt.Errorf("protojson.Unmarshal: %w", err)
}
err = cfg.ValidateAll()
if err != nil {
return nil, fmt.Errorf("kascfg.Validate: %w", err)
}
return cfg, nil
}
func NewCommand() *cobra.Command {
a := App{
OwnPrivateApiUrl: os.Getenv(envVarOwnPrivateApiUrl),
OwnPrivateApiHost: os.Getenv(envVarOwnPrivateApiHost),
}
c := &cobra.Command{
Use: "kas",
Short: "GitLab Kubernetes Agent Server",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return a.Run(cmd.Context())
},
SilenceErrors: true,
SilenceUsage: true,
}
c.Flags().StringVar(&a.ConfigurationFile, "configuration-file", "", "Configuration file to use (YAML)")
cobra.CheckErr(c.MarkFlagRequired("configuration-file"))
return c
}
func loggerFromConfig(loggingCfg *kascfg.LoggingCF) (*zap.Logger, *zap.Logger, error) {
lockedSyncer := zapcore.Lock(logz.NoSync(os.Stderr))
level, err := logz.LevelFromString(loggingCfg.Level.String())
if err != nil {
return nil, nil, err
}
grpcLevel, err := logz.LevelFromString(loggingCfg.GrpcLevel.String())
if err != nil {
return nil, nil, err
}
return loggerWithLevel(level, lockedSyncer), loggerWithLevel(grpcLevel, lockedSyncer), nil
}
func loggerWithLevel(level zapcore.LevelEnabler, sync zapcore.WriteSyncer) *zap.Logger {
return zap.New(
zapcore.NewCore(
zapcore.NewJSONEncoder(logz.NewProductionEncoderConfig()),
sync,
level,
),
zap.ErrorOutput(sync),
)
}
|