-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
I changed the structure of the code so it more closely resembles the WSL-Pro-Service and Windows-Agent apps. The Landscape mock App matches the interface except that it is missing the Quit method. I did not implement it because I saw no reason to. This method is used only in signal handling, which we don't use in the Landscape mock.
- Loading branch information
1 parent
5eac990
commit afd2927
Showing
2 changed files
with
213 additions
and
174 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
package main | ||
|
||
import ( | ||
"bufio" | ||
"context" | ||
"errors" | ||
"fmt" | ||
"log/slog" | ||
"net" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
|
||
landscapeapi "github.com/canonical/landscape-hostagent-api" | ||
"github.com/canonical/ubuntu-pro-for-windows/mocks/landscape/landscapemockservice" | ||
"github.com/spf13/cobra" | ||
"google.golang.org/grpc" | ||
) | ||
|
||
// App encapsulate commands of the REPL. | ||
type App struct { | ||
rootCmd *cobra.Command | ||
} | ||
|
||
// New registers commands and returns a new App. | ||
func New() *App { | ||
var a App | ||
a.rootCmd = &cobra.Command{ | ||
Use: executableName(), | ||
Short: "A mock server for Landscape hostagent testing", | ||
Long: `Landscape mock REPL mocks a Landscape hostagent server | ||
on your command line. Hosted at the specified address.`, | ||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { | ||
// Force a visit of the local flags so persistent flags for all parents are merged. | ||
cmd.LocalFlags() | ||
|
||
// command parsing has been successful. Returns to not print usage anymore. | ||
cmd.SilenceUsage = true | ||
|
||
v := cmd.Flag("verbosity").Value.String() | ||
n, err := strconv.Atoi(v) | ||
if err != nil { | ||
return fmt.Errorf("could not parse verbosity: %v", err) | ||
} | ||
|
||
setVerboseMode(n) | ||
return nil | ||
}, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
ctx := context.Background() | ||
|
||
addr := cmd.Flag("address").Value.String() | ||
fmt.Printf("Hosting on %s\n", addr) | ||
|
||
populateCommands() | ||
fmt.Println(`Write "help" to see a list of available commands`) | ||
|
||
var cfg net.ListenConfig | ||
lis, err := cfg.Listen(ctx, "tcp", addr) | ||
if err != nil { | ||
slog.Error(fmt.Sprintf("Can't listen: %v", err)) | ||
return | ||
} | ||
defer lis.Close() | ||
|
||
server := grpc.NewServer() | ||
service := landscapemockservice.New() | ||
landscapeapi.RegisterLandscapeHostAgentServer(server, service) | ||
|
||
go func() { | ||
err := server.Serve(lis) | ||
if err != nil { | ||
slog.Error(fmt.Sprintf("Server exited with an error: %v", err)) | ||
} | ||
}() | ||
defer server.Stop() | ||
|
||
if err := a.run(ctx, service); err != nil { | ||
slog.Error(err.Error()) | ||
return | ||
} | ||
}, | ||
} | ||
|
||
a.rootCmd.PersistentFlags().CountP("verbosity", "v", "WARNING (-v) INFO (-vv), DEBUG (-vvv)") | ||
a.rootCmd.Flags().StringP("address", "a", "localhost:8000", "Overrides the address where the server will be hosted") | ||
|
||
return &a | ||
} | ||
|
||
// Run executes the command and associated process. It returns an error on syntax/usage error. | ||
func (a *App) Run() error { | ||
if a.rootCmd == nil { | ||
return errors.New("root command was not populated") | ||
} | ||
|
||
return a.rootCmd.Execute() | ||
} | ||
|
||
// UsageError returns if the error is a command parsing or runtime one. | ||
func (a *App) UsageError() bool { | ||
return !a.rootCmd.SilenceUsage | ||
} | ||
|
||
func executableName() string { | ||
exe, err := os.Executable() | ||
if err != nil { | ||
return "landscaperepl" | ||
} | ||
return filepath.Base(exe) | ||
} | ||
|
||
// setVerboseMode changes the verbosity of the logs. | ||
func setVerboseMode(n int) { | ||
var level slog.Level | ||
switch n { | ||
case 0: | ||
level = slog.LevelError | ||
case 1: | ||
level = slog.LevelWarn | ||
case 2: | ||
level = slog.LevelInfo | ||
default: | ||
level = slog.LevelDebug | ||
} | ||
|
||
h := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}) | ||
slog.SetDefault(slog.New(h)) | ||
} | ||
|
||
// run contains the main execution loop. | ||
func (a *App) run(ctx context.Context, s *landscapemockservice.Service) error { | ||
sc := bufio.NewScanner(os.Stdin) | ||
|
||
// READ | ||
for sc.Scan() { | ||
line := strings.TrimSpace(sc.Text()) | ||
|
||
if len(line) == 0 { | ||
continue | ||
} | ||
|
||
if strings.HasPrefix(line, "#") { | ||
continue | ||
} | ||
|
||
// EXECUTE + PRINT | ||
done := executeCommand(ctx, s, line) | ||
if done { | ||
break | ||
} | ||
|
||
// LOOP | ||
fmt.Println() | ||
} | ||
|
||
if err := sc.Err(); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type wrongUsageError struct{} | ||
|
||
func (err wrongUsageError) Error() string { | ||
return "wrong usage" | ||
} | ||
|
||
type exitError struct{} | ||
|
||
func (exitError) Error() string { | ||
return "exiting" | ||
} | ||
|
||
func executeCommand(ctx context.Context, s *landscapemockservice.Service, command string) (exit bool) { | ||
fields := strings.Fields(command) | ||
|
||
verb := fields[0] | ||
args := fields[1:] | ||
|
||
cmd, ok := commands[verb] | ||
if !ok { | ||
fmt.Fprintf(os.Stderr, "unknown verb %q. use 'help' to see available commands.\n", verb) | ||
return false | ||
} | ||
|
||
err := cmd.callback(ctx, s, args...) | ||
if errors.Is(err, exitError{}) { | ||
return true | ||
} | ||
if errors.Is(err, wrongUsageError{}) { | ||
fmt.Fprintln(os.Stderr, "Error: wrong usage:") | ||
showHelp(os.Stderr, verb) | ||
return false | ||
} | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "Error: %v\n", err) | ||
return false | ||
} | ||
|
||
return false | ||
} |
Oops, something went wrong.