Merged CLI and server app init

This commit is contained in:
Peter 2021-01-18 18:35:13 +01:00
parent 37c87adaf3
commit 0138b57782
Signed by: prskr
GPG key ID: C1DB5D2E8DB512F9
11 changed files with 302 additions and 165 deletions

View file

@ -9,12 +9,19 @@ import 'pkg/audit/event_entity.proto';
package inetmock.rpc;
enum FileOpenMode {
TRUNCATE = 0;
APPEND = 1;
}
message WatchEventsRequest {
string watcherName = 1;
}
message RegisterFileSinkRequest {
string targetPath = 1;
FileOpenMode openMode = 2;
uint32 permissions = 3;
}
message RegisterFileSinkResponse {

View file

@ -24,6 +24,13 @@ var (
Args: cobra.ExactArgs(1),
RunE: runRemoveFile,
}
readFileCmd = &cobra.Command{
Use: "readFile",
Short: "reads an audit file and prints the events",
Args: cobra.ExactArgs(1),
RunE: runReadFile,
}
)
func runAddFile(_ *cobra.Command, args []string) (err error) {
@ -35,7 +42,7 @@ func runAddFile(_ *cobra.Command, args []string) (err error) {
}
auditClient := rpc.NewAuditClient(conn)
ctx, cancel := context.WithTimeout(appCtx, grpcTimeout)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
_, err = auditClient.RegisterFileSink(ctx, &rpc.RegisterFileSinkRequest{TargetPath: args[0]})
@ -44,9 +51,13 @@ func runAddFile(_ *cobra.Command, args []string) (err error) {
func runRemoveFile(_ *cobra.Command, args []string) (err error) {
auditClient := rpc.NewAuditClient(conn)
ctx, cancel := context.WithTimeout(appCtx, grpcTimeout)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
_, err = auditClient.RemoveFileSink(ctx, &rpc.RemoveFileSinkRequest{TargetPath: args[0]})
return
}
func runReadFile(_ *cobra.Command, args []string) (err error) {
return
}

View file

@ -29,7 +29,7 @@ func watchAuditEvents(_ *cobra.Command, _ []string) (err error) {
auditClient := rpc.NewAuditClient(conn)
var watchClient rpc.Audit_WatchEventsClient
if watchClient, err = auditClient.WatchEvents(appCtx, &rpc.WatchEventsRequest{WatcherName: listenerName}); err != nil {
if watchClient, err = auditClient.WatchEvents(cliApp.Context(), &rpc.WatchEventsRequest{WatcherName: listenerName}); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
@ -47,7 +47,7 @@ func watchAuditEvents(_ *cobra.Command, _ []string) (err error) {
}
}()
<-appCtx.Done()
<-cliApp.Context().Done()
err = watchClient.CloseSend()
return

View file

@ -1,75 +0,0 @@
package main
import (
"context"
"os"
"os/signal"
"os/user"
"syscall"
"time"
"github.com/google/uuid"
"github.com/spf13/cobra"
"google.golang.org/grpc"
)
var (
cliCmd = &cobra.Command{
Use: "",
Short: "IMCTL is the CLI app to interact with an INetMock server",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return initGRPCConnection()
},
}
inetMockSocketPath string
outputFormat string
grpcTimeout time.Duration
appCtx context.Context
appCancel context.CancelFunc
conn *grpc.ClientConn
)
func init() {
cliCmd.PersistentFlags().StringVar(&inetMockSocketPath, "socket-path", "unix:///var/run/inetmock.sock", "Path to the INetMock socket file")
cliCmd.PersistentFlags().StringVarP(&outputFormat, "format", "f", "table", "Output format to use. Possible values: table, json, yaml")
cliCmd.PersistentFlags().DurationVar(&grpcTimeout, "grpc-timeout", 5*time.Second, "Timeout to connect to the gRPC API")
cliCmd.AddCommand(endpointsCmd, handlerCmd, healthCmd, auditCmd)
endpointsCmd.AddCommand(getEndpoints)
handlerCmd.AddCommand(getHandlersCmd)
healthCmd.AddCommand(generalHealthCmd, containerHealthCmd)
currentUser := ""
if usr, err := user.Current(); err == nil {
currentUser = usr.Username
} else {
currentUser = uuid.New().String()
}
watchEventsCmd.PersistentFlags().StringVar(
&listenerName,
"listener-name",
currentUser,
"set listener name - defaults to the current username, if the user cannot be determined a random UUID will be used",
)
auditCmd.AddCommand(watchEventsCmd, addFileCmd, removeFileCmd)
}
func initGRPCConnection() (err error) {
appCtx, appCancel = context.WithCancel(context.Background())
signals := make(chan os.Signal, 1)
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
go func() {
<-signals
appCancel()
}()
dialCtx, cancel := context.WithTimeout(appCtx, grpcTimeout)
conn, err = grpc.DialContext(dialCtx, inetMockSocketPath, grpc.WithInsecure())
cancel()
return
}

View file

@ -51,7 +51,7 @@ func fromEndpoints(eps []*rpc.Endpoint) (out []*printableEndpoint) {
func runGetEndpoints(_ *cobra.Command, _ []string) (err error) {
endpointsClient := rpc.NewEndpointsClient(conn)
ctx, cancel := context.WithTimeout(appCtx, grpcTimeout)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
var endpointsResp *rpc.GetEndpointsResponse
if endpointsResp, err = endpointsClient.GetEndpoints(ctx, &rpc.GetEndpointsRequest{}); err != nil {

View file

@ -40,7 +40,7 @@ func fromHandlers(hs []string) (handlers []*printableHandler) {
func runGetHandlers(_ *cobra.Command, _ []string) {
handlersClient := rpc.NewHandlersClient(conn)
ctx, cancel := context.WithTimeout(appCtx, grpcTimeout)
ctx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
defer cancel()
var err error
var handlersResp *rpc.GetHandlersResponse

View file

@ -1,8 +1,63 @@
package main
import (
"context"
"os/user"
"time"
"github.com/google/uuid"
"github.com/spf13/cobra"
"gitlab.com/inetmock/inetmock/internal/app"
"google.golang.org/grpc"
)
var (
inetMockSocketPath string
outputFormat string
grpcTimeout time.Duration
cliApp app.App
conn *grpc.ClientConn
)
func main() {
defer appCancel()
if err := cliCmd.Execute(); err != nil {
panic(err)
endpointsCmd.AddCommand(getEndpoints)
handlerCmd.AddCommand(getHandlersCmd)
healthCmd.AddCommand(generalHealthCmd, containerHealthCmd)
cliApp = app.NewApp("imctl", "IMCTL is the CLI app to interact with an INetMock server").
WithCommands(endpointsCmd, handlerCmd, healthCmd, auditCmd).
WithInitTasks(func(_ *cobra.Command, _ []string) (err error) {
return initGRPCConnection()
})
cliApp.RootCommand().PersistentFlags().StringVar(&inetMockSocketPath, "socket-path", "unix:///var/run/inetmock.sock", "Path to the INetMock socket file")
cliApp.RootCommand().PersistentFlags().StringVarP(&outputFormat, "format", "f", "table", "Output format to use. Possible values: table, json, yaml")
cliApp.RootCommand().PersistentFlags().DurationVar(&grpcTimeout, "grpc-timeout", 5*time.Second, "Timeout to connect to the gRPC API")
currentUser := ""
if usr, err := user.Current(); err == nil {
currentUser = usr.Username
} else {
currentUser = uuid.New().String()
}
watchEventsCmd.PersistentFlags().StringVar(
&listenerName,
"listener-name",
currentUser,
"set listener name - defaults to the current username, if the user cannot be determined a random UUID will be used",
)
auditCmd.AddCommand(watchEventsCmd, addFileCmd, removeFileCmd)
cliApp.MustRun()
}
func initGRPCConnection() (err error) {
dialCtx, cancel := context.WithTimeout(cliApp.Context(), grpcTimeout)
conn, err = grpc.DialContext(dialCtx, inetMockSocketPath, grpc.WithInsecure())
cancel()
return
}

View file

@ -2,19 +2,13 @@ package main
import (
"fmt"
"os"
"gitlab.com/inetmock/inetmock/internal/app"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
_ "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
_ "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
mock2 "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
dns "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
http "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/proxy"
_ "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/proxy"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/metrics"
_ "gitlab.com/inetmock/inetmock/internal/endpoint/handler/metrics"
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/tls/interceptor"
_ "gitlab.com/inetmock/inetmock/internal/endpoint/handler/tls/interceptor"
"go.uber.org/zap"
)
@ -30,18 +24,19 @@ func main() {
}
}()
var err error
if server, err = app.NewApp(
mock2.AddHTTPMock,
mock.AddDNSMock,
interceptor.AddTLSInterceptor,
proxy.AddHTTPProxy,
metrics.AddMetricsExporter,
); err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
server.
app.NewApp("inetmock", "INetMock is lightweight internet mock").
WithHandlerRegistry(
http.AddHTTPMock,
dns.AddDNSMock,
interceptor.AddTLSInterceptor,
proxy.AddHTTPProxy,
metrics.AddMetricsExporter).
WithCommands(serveCmd, generateCaCmd).
WithConfig().
WithLogger().
WithHealthChecker().
WithCertStore().
WithEventStream().
WithEndpointManager().
MustRun()
}

1
go.mod
View file

@ -14,6 +14,7 @@ require (
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.1
go.uber.org/multierr v1.5.0
go.uber.org/zap v1.16.0
google.golang.org/grpc v1.34.0
google.golang.org/protobuf v1.25.0

View file

@ -18,6 +18,7 @@ import (
"gitlab.com/inetmock/inetmock/pkg/health"
"gitlab.com/inetmock/inetmock/pkg/logging"
"gitlab.com/inetmock/inetmock/pkg/path"
"go.uber.org/multierr"
"go.uber.org/zap"
)
@ -27,6 +28,18 @@ var (
developmentLogs bool
)
type contextKey string
const (
loggerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/logger"
configKey contextKey = "gitlab.com/inetmock/inetmock/app/context/config"
handlerRegistryKey contextKey = "gitlab.com/inetmock/inetmock/app/context/handlerRegistry"
healthCheckerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/healthChecker"
endpointManagerKey contextKey = "gitlab.com/inetmock/inetmock/app/context/endpointManager"
certStoreKey contextKey = "gitlab.com/inetmock/inetmock/app/context/certStore"
eventStreamKey contextKey = "gitlab.com/inetmock/inetmock/app/context/eventStream"
)
type App interface {
api.PluginContext
EventStream() audit.EventStream
@ -35,28 +48,56 @@ type App interface {
EndpointManager() endpoint.EndpointManager
HandlerRegistry() api.HandlerRegistry
Context() context.Context
RootCommand() *cobra.Command
MustRun()
Shutdown()
// WithCommands adds subcommands to the root command
// requires nothing
WithCommands(cmds ...*cobra.Command) App
// WithHandlerRegistry builds up the handler registry
// requires nothing
WithHandlerRegistry(registrations ...api.Registration) App
// WithHealthChecker adds the health checker mechanism
// requires nothing
WithHealthChecker() App
// WithLogger configures the logging system
// requires nothing
WithLogger() App
// WithEndpointManager creates an endpoint manager instance and adds it to the context
// requires WithHandlerRegistry, WithHealthChecker and WithLogger
WithEndpointManager() App
// WithCertStore initializes the cert store
// requires WithLogger and WithConfig
WithCertStore() App
// WithEventStream adds the audit event stream
// requires WithLogger
WithEventStream() App
// WithConfig loads the config
// requires nothing
WithConfig() App
WithInitTasks(task ...func(cmd *cobra.Command, args []string) (err error)) App
}
type app struct {
cfg config.Config
rootCmd *cobra.Command
rootLogger logging.Logger
certStore cert.Store
checker health.Checker
endpointManager endpoint.EndpointManager
registry api.HandlerRegistry
ctx context.Context
cancel context.CancelFunc
eventStream audit.EventStream
rootCmd *cobra.Command
ctx context.Context
cancel context.CancelFunc
lateInitTasks []func(cmd *cobra.Command, args []string) (err error)
}
func (a *app) MustRun() {
if err := a.rootCmd.Execute(); err != nil {
if a.rootLogger != nil {
a.rootLogger.Error(
if a.Logger() != nil {
a.Logger().Error(
"Failed to run inetmock",
zap.Error(err),
)
@ -66,107 +107,146 @@ func (a *app) MustRun() {
}
}
func (a app) Logger() logging.Logger {
return a.rootLogger
func (a *app) Logger() logging.Logger {
return a.ctx.Value(loggerKey).(logging.Logger)
}
func (a app) Config() config.Config {
return a.cfg
func (a *app) Config() config.Config {
return a.ctx.Value(configKey).(config.Config)
}
func (a app) CertStore() cert.Store {
return a.certStore
func (a *app) CertStore() cert.Store {
return a.ctx.Value(certStoreKey).(cert.Store)
}
func (a app) Checker() health.Checker {
return a.checker
func (a *app) Checker() health.Checker {
return a.ctx.Value(healthCheckerKey).(health.Checker)
}
func (a app) EndpointManager() endpoint.EndpointManager {
return a.endpointManager
func (a *app) EndpointManager() endpoint.EndpointManager {
return a.ctx.Value(endpointManagerKey).(endpoint.EndpointManager)
}
func (a app) Audit() audit.Emitter {
return a.eventStream
func (a *app) Audit() audit.Emitter {
return a.ctx.Value(eventStreamKey).(audit.Emitter)
}
func (a app) EventStream() audit.EventStream {
return a.eventStream
func (a *app) EventStream() audit.EventStream {
return a.ctx.Value(eventStreamKey).(audit.EventStream)
}
func (a app) HandlerRegistry() api.HandlerRegistry {
return a.registry
func (a *app) HandlerRegistry() api.HandlerRegistry {
return a.ctx.Value(handlerRegistryKey).(api.HandlerRegistry)
}
func (a app) Context() context.Context {
func (a *app) Context() context.Context {
return a.ctx
}
func (a app) Shutdown() {
func (a *app) RootCommand() *cobra.Command {
return a.rootCmd
}
func (a *app) Shutdown() {
a.cancel()
}
// WithCommands adds subcommands to the root command
// requires nothing
func (a *app) WithCommands(cmds ...*cobra.Command) App {
a.rootCmd.AddCommand(cmds...)
return a
}
func NewApp(registrations ...api.Registration) (inetmockApp App, err error) {
// WithHandlerRegistry builds up the handler registry
// requires nothing
func (a *app) WithHandlerRegistry(registrations ...api.Registration) App {
registry := api.NewHandlerRegistry()
for _, registration := range registrations {
if err = registration(registry); err != nil {
return
if err := registration(registry); err != nil {
panic(err)
}
}
ctx, cancel := initAppContext()
a.ctx = context.WithValue(a.ctx, handlerRegistryKey, registry)
a := &app{
rootCmd: &cobra.Command{
Short: "INetMock is lightweight internet mock",
},
checker: health.New(),
registry: registry,
ctx: ctx,
cancel: cancel,
}
return a
}
a.rootCmd.PersistentFlags().StringVar(&configFilePath, "config", "", "Path to config file that should be used")
a.rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "logging level to use")
a.rootCmd.PersistentFlags().BoolVar(&developmentLogs, "development-logs", false, "Enable development mode logs")
// WithHealthChecker adds the health checker mechanism
// requires nothing
func (a *app) WithHealthChecker() App {
checker := health.New()
a.ctx = context.WithValue(a.ctx, healthCheckerKey, checker)
return a
}
a.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) {
// WithLogger configures the logging system
// requires nothing
func (a *app) WithLogger() App {
a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, args []string) (err error) {
logging.ConfigureLogging(
logging.ParseLevel(logLevel),
developmentLogs,
map[string]interface{}{
"cwd": path.WorkingDirectory(),
"cwd": path.WorkingDirectory(),
"cmd": cmd.Name(),
"args": args,
},
)
if a.rootLogger, err = logging.CreateLogger(); err != nil {
var logger logging.Logger
if logger, err = logging.CreateLogger(); err != nil {
return
}
a.ctx = context.WithValue(a.ctx, loggerKey, logger)
return
})
return a
}
a.endpointManager = endpoint.NewEndpointManager(
a.registry,
// WithEndpointManager creates an endpoint manager instance and adds it to the context
// requires WithHandlerRegistry, WithHealthChecker and WithLogger
func (a *app) WithEndpointManager() App {
a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) {
epMgr := endpoint.NewEndpointManager(
a.HandlerRegistry(),
a.Logger().Named("EndpointManager"),
a.checker,
a.Checker(),
a,
)
a.cfg = config.CreateConfig(cmd.Flags())
a.ctx = context.WithValue(a.ctx, endpointManagerKey, epMgr)
return
})
return a
}
if err = a.cfg.ReadConfig(configFilePath); err != nil {
// WithCertStore initializes the cert store
// requires WithLogger and WithConfig
func (a *app) WithCertStore() App {
a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, args []string) (err error) {
var certStore cert.Store
if certStore, err = cert.NewDefaultStore(
a.Config(),
a.Logger().Named("CertStore"),
); err != nil {
return
}
if a.certStore, err = cert.NewDefaultStore(a.cfg, a.rootLogger); err != nil {
return
}
a.ctx = context.WithValue(a.ctx, certStoreKey, certStore)
return
})
return a
}
a.eventStream, err = audit.NewEventStream(
// WithEventStream adds the audit event stream
// requires WithLogger
func (a *app) WithEventStream() App {
a.lateInitTasks = append(a.lateInitTasks, func(_ *cobra.Command, _ []string) (err error) {
var eventStream audit.EventStream
eventStream, err = audit.NewEventStream(
a.Logger().Named("EventStream"),
audit.WithSinkBufferSize(10),
)
@ -174,11 +254,60 @@ func NewApp(registrations ...api.Registration) (inetmockApp App, err error) {
return
}
err = a.eventStream.RegisterSink(sink.NewLogSink(a.Logger().Named("LogSink")))
if err = eventStream.RegisterSink(sink.NewLogSink(a.Logger().Named("LogSink"))); err != nil {
return
}
a.ctx = context.WithValue(a.ctx, eventStreamKey, eventStream)
return
})
return a
}
// WithConfig loads the config
// requires nothing
func (a *app) WithConfig() App {
a.lateInitTasks = append(a.lateInitTasks, func(cmd *cobra.Command, _ []string) (err error) {
cfg := config.CreateConfig(cmd.Flags())
if err = cfg.ReadConfig(configFilePath); err != nil {
return
}
a.ctx = context.WithValue(a.ctx, configKey, cfg)
return
})
return a
}
func (a *app) WithInitTasks(task ...func(cmd *cobra.Command, args []string) (err error)) App {
a.lateInitTasks = append(a.lateInitTasks, task...)
return a
}
func NewApp(name, short string) App {
ctx, cancel := initAppContext()
a := &app{
rootCmd: &cobra.Command{
Use: name,
Short: short,
},
ctx: ctx,
cancel: cancel,
}
a.rootCmd.PersistentFlags().StringVar(&configFilePath, "config", "", "Path to config file that should be used")
a.rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "logging level to use")
a.rootCmd.PersistentFlags().BoolVar(&developmentLogs, "development-logs", false, "Enable development mode logs")
a.rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) (err error) {
for _, initTask := range a.lateInitTasks {
err = multierr.Append(err, initTask(cmd, args))
}
return
}
return a, nil
return a
}
func initAppContext() (context.Context, context.CancelFunc) {

View file

@ -36,7 +36,21 @@ func (a *auditServer) WatchEvents(req *WatchEventsRequest, srv Audit_WatchEvents
func (a *auditServer) RegisterFileSink(_ context.Context, req *RegisterFileSinkRequest) (resp *RegisterFileSinkResponse, err error) {
var writer io.WriteCloser
if writer, err = os.OpenFile(req.TargetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
var flags int
switch req.OpenMode {
case FileOpenMode_APPEND:
flags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
default:
flags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
}
var permissions = os.FileMode(req.Permissions)
if permissions == 0 {
permissions = 644
}
if writer, err = os.OpenFile(req.TargetPath, flags, permissions); err != nil {
return
}
if err = a.eventStream.RegisterSink(sink.NewWriterSink(req.TargetPath, audit.NewEventWriter(writer))); err != nil {