From d89ab3a57629d927260e95e252e0df4dd9f705ac Mon Sep 17 00:00:00 2001 From: Peter Kurfer Date: Sun, 3 May 2020 10:18:28 +0200 Subject: [PATCH] Split to server and CLI binaries - introduce CLI binary - move server binary to subdirectory to have a uniformed directory structure - update goreleaser to build all binaries and create dist packages - update ignore files accordingly --- .dockerignore | 2 + .gitignore | 5 ++- .goreleaser.yml | 37 ++++++++++++++--- Makefile | 29 ++++++++----- cmd/imctl/main.go | 9 +++++ main.go => cmd/inetmock/main.go | 2 +- internal/cmd/ca.go | 2 +- internal/cmd/cli.go | 27 +++++++++++++ internal/cmd/endpoints.go | 72 +++++++++++++++++++++++++++++++++ internal/cmd/handlers.go | 61 ++++++++++++++++++++++++++++ internal/cmd/serve.go | 62 ++++++++++++++++++++++++++++ 11 files changed, 287 insertions(+), 21 deletions(-) create mode 100644 cmd/imctl/main.go rename main.go => cmd/inetmock/main.go (82%) create mode 100644 internal/cmd/cli.go create mode 100644 internal/cmd/endpoints.go create mode 100644 internal/cmd/handlers.go diff --git a/.dockerignore b/.dockerignore index 1446b07..adb9b7d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,8 +3,10 @@ ############### .git/ +.github/ .idea/ .vscode/ +api/ deploy/ dist/ doc/ diff --git a/.gitignore b/.gitignore index 01fe770..4ea0312 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,9 @@ **/*.so *.key *.pem -inetmock -main +/inetmock +/imctl +./main ############### # directories # diff --git a/.goreleaser.yml b/.goreleaser.yml index dad29e5..0a4bdf4 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -5,24 +5,49 @@ before: # You may remove this if you don't use go modules. - make plugins builds: - - id: "default" + - id: "inetmock" + binary: inetmock + main: ./cmd/inetmock/main.go + ldflags: + - -w -s + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 + - id: "imctl" + binary: imctl + main: ./cmd/imctl/main.go ldflags: - -w -s goos: - - linux + - linux + - freebsd + - darwin + - windows goarch: - - amd64 + - amd64 archives: - - id: default + - id: inetmock builds: - - default - name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + - inetmock + name_template: "{{ .ProjectName }}_server_{{ .Version }}_{{ .Os }}_{{ .Arch }}" replacements: amd64: x86_64 wrap_in_directory: true files: - config.yaml - "*.so" + - id: imctl + builds: + - imctl + name_template: "{{ .ProjectName }}_cli_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + replacements: + amd64: x86_64 + wrap_in_directory: true + files: [] checksum: name_template: 'checksums.txt' snapshot: diff --git a/Makefile b/Makefile index b1dec72..3744ec8 100644 --- a/Makefile +++ b/Makefile @@ -1,50 +1,57 @@ VERSION = $(shell git describe --dirty --tags --always) DIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) -BUILD_PATH = $(DIR)/main.go +SERVER_BUILD_PATH = github.com/baez90/inetmock/cmd/inetmock +CLI_BUILD_PATH = github.com/baez90/inetmock/cmd/imctl PKGS = $(shell go list ./...) TEST_PKGS = $(shell go list ./...) PROTO_FILES = $(shell find $(DIR)api/ -type f -name "*.proto") GOARGS = GOOS=linux GOARCH=amd64 -GO_BUILD_ARGS = -ldflags="-w -s" -GO_CONTAINER_BUILD_ARGS = -ldflags="-w -s" -a -installsuffix cgo +GO_BUILD_ARGS = -ldflags='-w -s' +GO_CONTAINER_BUILD_ARGS = -ldflags='-w -s' -a -installsuffix cgo GO_DEBUG_BUILD_ARGS = -gcflags "all=-N -l" -BINARY_NAME = inetmock +SERVER_BINARY_NAME = inetmock +CLI_BINARY_NAME = imctl PLUGINS = $(wildcard $(DIR)plugins/*/.) DEBUG_PORT = 2345 DEBUG_ARGS?= --development-logs=true CONTAINER_BUILDER ?= podman DOCKER_IMAGE ?= inetmock -.PHONY: clean all format deps update-deps compile debug generate protoc snapshot-release test cli-cover-report html-cover-report plugins $(PLUGINS) $(GO_GEN_FILES) +.PHONY: clean all format deps update-deps compile compile-server compile-cli debug generate protoc snapshot-release test cli-cover-report html-cover-report plugins $(PLUGINS) $(GO_GEN_FILES) all: clean format compile test plugins clean: @find $(DIR) -type f \( -name "*.out" -or -name "*.so" \) -exec rm -f {} \; @rm -rf $(DIR)*.so - @rm -f $(DIR)$(BINARY_NAME) $(DIR)main + @rm -f $(DIR)$(SERVER_BINARY_NAME) $(DIR)$(CLI_BINARY_NAME) $(DIR)main format: @go fmt $(PKGS) deps: - @go build -v $(BUILD_PATH) + @go build -v $(SERVER_BUILD_PATH) update-deps: @go mod tidy @go get -u -compile: deps +compile-server: deps ifdef DEBUG @echo 'Compiling for debugging...' - @$(GOARGS) go build $(GO_DEBUG_BUILD_ARGS) -o $(DIR)$(BINARY_NAME) $(BUILD_PATH) + @$(GOARGS) go build $(GO_DEBUG_BUILD_ARGS) -o $(DIR)$(SERVER_BINARY_NAME) $(SERVER_BUILD_PATH) else ifdef CONTAINER @echo 'Compiling for container usage...' - @$(GOARGS) go build $(GO_CONTAINER_BUILD_ARGS) -o $(DIR)$(BINARY_NAME) $(BUILD_PATH) + @$(GOARGS) go build $(GO_CONTAINER_BUILD_ARGS) -o $(DIR)$(SERVER_BINARY_NAME) $(SERVER_BUILD_PATH) else @echo 'Compiling for normal Linux env...' - @$(GOARGS) go build $(GO_BUILD_ARGS) -o $(DIR)$(BINARY_NAME) $(BUILD_PATH) + @$(GOARGS) go build $(GO_BUILD_ARGS) -o $(DIR)$(SERVER_BINARY_NAME) $(SERVER_BUILD_PATH) endif +compile-cli: deps + @$(GOARGS) go build $(GO_BUILD_ARGS) -o $(CLI_BINARY_NAME) $(CLI_BUILD_PATH) + +compile: compile-server compile-cli + debug: export INETMOCK_PLUGINS_DIRECTORY = $(DIR) debug: dlv debug $(DIR) \ diff --git a/cmd/imctl/main.go b/cmd/imctl/main.go new file mode 100644 index 0000000..8db7efb --- /dev/null +++ b/cmd/imctl/main.go @@ -0,0 +1,9 @@ +package main + +import "github.com/baez90/inetmock/internal/cmd" + +func main() { + if err := cmd.ExecuteClientCommand(); err != nil { + panic(err) + } +} diff --git a/main.go b/cmd/inetmock/main.go similarity index 82% rename from main.go rename to cmd/inetmock/main.go index 206a226..04370e3 100644 --- a/main.go +++ b/cmd/inetmock/main.go @@ -10,7 +10,7 @@ func main() { logger, _ := zap.NewProduction() defer logger.Sync() - if err := cmd.ExecuteRootCommand(); err != nil { + if err := cmd.ExecuteServerCommand(); err != nil { logger.Error("Failed to run inetmock", zap.Error(err), ) diff --git a/internal/cmd/ca.go b/internal/cmd/ca.go index c4fb97f..6a11ff5 100644 --- a/internal/cmd/ca.go +++ b/internal/cmd/ca.go @@ -53,7 +53,7 @@ func init() { generateCaCmd.Flags().Duration(generateCANotAfterRelative, 17520*time.Hour, "Relative time value until when in the future the CA certificate should be valid. The value has a time unit, the greatest time unit is h for hour.") } -func runGenerateCA(cmd *cobra.Command, args []string) { +func runGenerateCA(_ *cobra.Command, _ []string) { var certOutPath, curveName string var notBefore, notAfter time.Duration var err error diff --git a/internal/cmd/cli.go b/internal/cmd/cli.go new file mode 100644 index 0000000..44f3daa --- /dev/null +++ b/internal/cmd/cli.go @@ -0,0 +1,27 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "time" +) + +var ( + cliCmd = &cobra.Command{ + Use: "", + Short: "IMCTL is the CLI app to interact with an INetMock server", + } + + inetMockSocketPath string + outputFormat string + grpcTimeout time.Duration +) + +func init() { + cliCmd.PersistentFlags().StringVar(&inetMockSocketPath, "socket-path", "./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) + endpointsCmd.AddCommand(getEndpoints) + handlerCmd.AddCommand(getHandlersCmd) +} diff --git a/internal/cmd/endpoints.go b/internal/cmd/endpoints.go new file mode 100644 index 0000000..dc3c7f3 --- /dev/null +++ b/internal/cmd/endpoints.go @@ -0,0 +1,72 @@ +package cmd + +import ( + "context" + "fmt" + "github.com/baez90/inetmock/internal/format" + "github.com/baez90/inetmock/internal/rpc" + "github.com/spf13/cobra" + "google.golang.org/grpc" + "os" +) + +var ( + getEndpoints = &cobra.Command{ + Use: "get", + Short: "Get all running endpoints", + Run: runGetEndpoints, + } + + endpointsCmd = &cobra.Command{ + Use: "endpoints", + Short: "endpoints is the entrypoint to all kind of commands to interact with endpoints", + Aliases: []string{"ep", "endpoint"}, + } +) + +type printableEndpoint struct { + Id string + Name string + Handler string + ListenAddress string + Port int +} + +func fromEndpoint(ep *rpc.Endpoint) *printableEndpoint { + return &printableEndpoint{ + Id: ep.Id, + Name: ep.Name, + Handler: ep.Handler, + ListenAddress: ep.ListenAddress, + Port: int(ep.Port), + } +} + +func fromEndpoints(eps []*rpc.Endpoint) (out []*printableEndpoint) { + for idx := range eps { + out = append(out, fromEndpoint(eps[idx])) + } + return +} + +func runGetEndpoints(_ *cobra.Command, _ []string) { + var err error + var conn *grpc.ClientConn + + if conn, err = grpc.Dial(inetMockSocketPath, grpc.WithInsecure()); err != nil { + fmt.Printf("Failed to connecto INetMock socket: %v\n", err) + os.Exit(10) + } + endpointsClient := rpc.NewEndpointsClient(conn) + ctx, _ := context.WithTimeout(context.Background(), grpcTimeout) + var endpointsResp *rpc.GetEndpointsResponse + if endpointsResp, err = endpointsClient.GetEndpoints(ctx, &rpc.GetEndpointsRequest{}); err != nil { + fmt.Printf("Failed to get the endpoints: %v", err) + os.Exit(11) + } + + writer := format.Writer(outputFormat, os.Stdout) + if err = writer.Write(fromEndpoints(endpointsResp.Endpoints)); err != nil { + fmt.Printf("Error occurred during writing response values: %v\n", err) + } +} diff --git a/internal/cmd/handlers.go b/internal/cmd/handlers.go new file mode 100644 index 0000000..a5b988b --- /dev/null +++ b/internal/cmd/handlers.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "context" + "fmt" + "github.com/baez90/inetmock/internal/format" + "github.com/baez90/inetmock/internal/rpc" + "github.com/spf13/cobra" + "google.golang.org/grpc" + "os" +) + +var ( + getHandlersCmd = &cobra.Command{ + Use: "get", + Short: "Get all registered handlers", + Run: runGetHandlers, + } + + handlerCmd = &cobra.Command{ + Use: "handlers", + Short: "handlers is the entrypoint to all kind of commands to interact with handlers", + Aliases: []string{"handler"}, + } +) + +type printableHandler struct { + Handler string +} + +func fromHandlers(hs []string) (handlers []*printableHandler) { + for idx := range hs { + handlers = append(handlers, &printableHandler{ + Handler: hs[idx], + }) + } + return +} + +func runGetHandlers(_ *cobra.Command, _ []string) { + var err error + var conn *grpc.ClientConn + + if conn, err = grpc.Dial(inetMockSocketPath, grpc.WithInsecure()); err != nil { + fmt.Printf("Failed to connecto INetMock socket: %v\n", err) + os.Exit(10) + } + handlersClient := rpc.NewHandlersClient(conn) + ctx, _ := context.WithTimeout(context.Background(), grpcTimeout) + var handlersResp *rpc.GetHandlersResponse + + if handlersResp, err = handlersClient.GetHandlers(ctx, &rpc.GetHandlersRequest{}); err != nil { + fmt.Printf("Failed to get the endpoints: %v", err) + os.Exit(11) + } + + writer := format.Writer(outputFormat, os.Stdout) + if err = writer.Write(fromHandlers(handlersResp.Handlers)); err != nil { + fmt.Printf("Error occurred during writing response values: %v\n", err) + } +} diff --git a/internal/cmd/serve.go b/internal/cmd/serve.go index b6b6d18..d09b7fe 100644 --- a/internal/cmd/serve.go +++ b/internal/cmd/serve.go @@ -2,13 +2,19 @@ package cmd import ( "github.com/baez90/inetmock/internal/endpoints" + "github.com/baez90/inetmock/internal/plugins" + "github.com/baez90/inetmock/internal/rpc" + "github.com/baez90/inetmock/pkg/api" "github.com/baez90/inetmock/pkg/config" + "github.com/baez90/inetmock/pkg/logging" + "github.com/baez90/inetmock/pkg/path" "github.com/spf13/cobra" "go.uber.org/zap" "os" "os/signal" "strings" "syscall" + "time" ) var ( @@ -21,9 +27,58 @@ var ( } ) +func onServerInit() { + logging.ConfigureLogging( + logging.ParseLevel(logLevel), + developmentLogs, + map[string]interface{}{"cwd": path.WorkingDirectory()}, + ) + + logger, _ = logging.CreateLogger() + config.CreateConfig(serverCmd.Flags()) + appConfig := config.Instance() + + if err := appConfig.ReadConfig(configFilePath); err != nil { + logger.Error( + "failed to read config file", + zap.Error(err), + ) + } + + if err := api.InitServices(appConfig, logger); err != nil { + logger.Error( + "failed to initialize app services", + zap.Error(err), + ) + } + + registry := plugins.Registry() + cfg := config.Instance() + + pluginLoadStartTime := time.Now() + if err := registry.LoadPlugins(cfg.PluginsDir()); err != nil { + logger.Error("Failed to load plugins", + zap.String("pluginsDirectory", cfg.PluginsDir()), + zap.Error(err), + ) + } + pluginLoadDuration := time.Since(pluginLoadStartTime) + logger.Info( + "loading plugins completed", + zap.Duration("pluginLoadDuration", pluginLoadDuration), + ) +} + func startINetMock(_ *cobra.Command, _ []string) { + onServerInit() endpointManager = endpoints.NewEndpointManager(logger) cfg := config.Instance() + rpcAPI := rpc.NewINetMockAPI( + cfg, + endpointManager, + plugins.Registry(), + ) + for endpointName, endpointHandler := range cfg.EndpointConfigs() { handlerSubConfig := cfg.Viper().Sub(strings.Join([]string{config.EndpointsKey, endpointName, config.OptionsKey}, ".")) endpointHandler.Options = handlerSubConfig @@ -38,6 +93,12 @@ func startINetMock(_ *cobra.Command, _ []string) { } endpointManager.StartEndpoints() + if err := rpcAPI.StartServer(); err != nil { + logger.Error( + "failed to start gRPC API", + zap.Error(err), + ) + } signalChannel := make(chan os.Signal, 1) signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) @@ -50,5 +111,6 @@ func startINetMock(_ *cobra.Command, _ []string) { zap.String("signal", s.String()), ) + rpcAPI.StopServer() endpointManager.ShutdownEndpoints() }