feat(setup): build start and verify ThothII
This commit is contained in:
+18
-26
@@ -91,7 +91,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
return 0
|
||||
}
|
||||
if command == "setup" {
|
||||
return setupCommand(installationPath, commandArgs, stdout, stderr)
|
||||
return setupCommand(ctx, installationPath, commandArgs, stdout, stderr)
|
||||
}
|
||||
return commandUsageError(stderr, fmt.Sprintf("unknown command %q", command))
|
||||
}
|
||||
@@ -205,7 +205,21 @@ func isBootstrapCommand(command string) bool {
|
||||
return command == "help" || command == "setup" || command == "version"
|
||||
}
|
||||
|
||||
func setupCommand(installationPath string, args []string, stdout, stderr io.Writer) int {
|
||||
type setupExecutor func(context.Context, compose.Runner, setup.Request, io.Reader, io.Writer) (setup.Result, error)
|
||||
|
||||
func setupCommand(ctx context.Context, installationPath string, args []string, stdout, stderr io.Writer) int {
|
||||
return setupCommandWith(ctx, installationPath, args, os.Stdin, compose.NewRunner(""), setup.Run, stdout, stderr)
|
||||
}
|
||||
|
||||
func setupCommandWith(
|
||||
ctx context.Context,
|
||||
installationPath string,
|
||||
args []string,
|
||||
input io.Reader,
|
||||
runner compose.Runner,
|
||||
execute setupExecutor,
|
||||
stdout, stderr io.Writer,
|
||||
) int {
|
||||
request, err := parseSetupArgs(args)
|
||||
if err != nil {
|
||||
return commandUsageError(stderr, err.Error())
|
||||
@@ -229,11 +243,10 @@ func setupCommand(installationPath string, args []string, stdout, stderr io.Writ
|
||||
}
|
||||
request.InstallationID = id
|
||||
}
|
||||
result, err := setup.EnsureFiles(request, os.Stdin, stdout)
|
||||
_, err = execute(ctx, runner, request, input, stdout)
|
||||
if err != nil {
|
||||
return commandUsageError(stderr, err.Error())
|
||||
}
|
||||
fmt.Fprintf(stdout, "Configuration is ready: %s\n", result.DescriptorPath)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -386,32 +399,11 @@ func serverOperationFailure(stderr io.Writer, err error, secretValues []string)
|
||||
return 1
|
||||
}
|
||||
|
||||
// installationRunner transforms only Compose invocations into the installation's validated,
|
||||
// profile-specific argument list. Direct Docker image commands remain host-side and use arguments.
|
||||
type installationRunner struct {
|
||||
installation config.Installation
|
||||
runner compose.Runner
|
||||
}
|
||||
|
||||
func (r installationRunner) SessionInventoryScope() string {
|
||||
if r.installation.Profile == "local" {
|
||||
return "mine"
|
||||
}
|
||||
return "all"
|
||||
}
|
||||
|
||||
func (r installationRunner) Run(ctx context.Context, args []string, stdin io.Reader) (compose.Result, error) {
|
||||
if len(args) > 0 && args[0] == "compose" {
|
||||
return r.runner.Run(ctx, r.installation.ComposeArgs(args[1:]...), stdin)
|
||||
}
|
||||
return r.runner.Run(ctx, args, stdin)
|
||||
}
|
||||
|
||||
func piCommand(ctx context.Context, installation config.Installation, runner compose.Runner, args []string, secretValues []string, stdout, stderr io.Writer) int {
|
||||
if len(args) == 0 {
|
||||
return commandUsageError(stderr, "pi requires a subcommand")
|
||||
}
|
||||
controlled := installationRunner{installation: installation, runner: runner}
|
||||
controlled := compose.InstallationRunner{Installation: installation, Runner: runner}
|
||||
switch args[0] {
|
||||
case "status":
|
||||
if len(args) != 1 {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/pi"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/setup"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/testsupport"
|
||||
)
|
||||
|
||||
@@ -25,7 +26,7 @@ func TestInstallationRunnerMapsProfileToSessionInventoryScope(t *testing.T) {
|
||||
{profile: "local", want: "mine"},
|
||||
{profile: "server", want: "all"},
|
||||
} {
|
||||
runner := installationRunner{installation: config.Installation{Profile: test.profile}}
|
||||
runner := compose.InstallationRunner{Installation: config.Installation{Profile: test.profile}}
|
||||
if got := runner.SessionInventoryScope(); got != test.want {
|
||||
t.Fatalf("profile %q maps to session scope %q, want %q", test.profile, got, test.want)
|
||||
}
|
||||
@@ -107,6 +108,50 @@ func TestBootstrapCommandsDoNotRequireAnInstallationDescriptor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupCommandBuildsAndStartsUnlessConfigureOnlyIsRequested(t *testing.T) {
|
||||
projectRoot := newProjectWithoutInstallation(t)
|
||||
previousDirectory, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chdir(projectRoot); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = os.Chdir(previousDirectory) })
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
args []string
|
||||
want bool
|
||||
}{
|
||||
{name: "default", args: []string{"--non-interactive", "--installation-id", "cli", "--profile", "local"}, want: false},
|
||||
{name: "configuration only", args: []string{"--configure-only", "--non-interactive", "--installation-id", "cli", "--profile", "local"}, want: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var received setup.Request
|
||||
executor := func(_ context.Context, _ compose.Runner, request setup.Request, _ io.Reader, _ io.Writer) (setup.Result, error) {
|
||||
received = request
|
||||
return setup.Result{DescriptorPath: filepath.Join(projectRoot, "deploy", "cli", "thothii-installation.yaml"), Configured: true, Built: !request.ConfigureOnly, Started: !request.ConfigureOnly, Healthy: !request.ConfigureOnly}, nil
|
||||
}
|
||||
var stdout, stderr bytes.Buffer
|
||||
code := setupCommandWith(context.Background(), "", test.args, strings.NewReader(""), compose.NewRunner("docker"), executor, &stdout, &stderr)
|
||||
if code != 0 {
|
||||
t.Fatalf("setupCommandWith() = %d, stderr = %q", code, stderr.String())
|
||||
}
|
||||
if received.ConfigureOnly != test.want {
|
||||
t.Fatalf("ConfigureOnly = %t, want %t", received.ConfigureOnly, test.want)
|
||||
}
|
||||
canonicalRoot, pathErr := filepath.EvalSymlinks(projectRoot)
|
||||
if pathErr != nil {
|
||||
t.Fatal(pathErr)
|
||||
}
|
||||
if received.ProjectRoot != canonicalRoot {
|
||||
t.Fatalf("ProjectRoot = %q, want %q", received.ProjectRoot, canonicalRoot)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Catches interactive configuration prompts that use retired model-only data instead of the
|
||||
// provider, model, and reasoning choices supplied by the dedicated Pi Management API.
|
||||
func TestResolvePiConfigureUsesNumberedClosedChoicesOnlyForTTY(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user