refactor(cli): rename operator command to tht
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// thothctl is the host-side operator command for a local ThothII installation.
|
||||
// tht is the host-side operator command for a local ThothII installation.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -14,15 +14,15 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/output"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/pi"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/serverops"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/workspaceops"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/output"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/pi"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/serverops"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/workspaceops"
|
||||
)
|
||||
|
||||
const usage = `Usage: thothctl --installation <absolute-path>/thothii-installation.yaml <command>
|
||||
const usage = `Usage: tht --installation <absolute-path>/thothii-installation.yaml <command>
|
||||
|
||||
Commands:
|
||||
status Show the Compose service state.
|
||||
@@ -75,22 +75,22 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
}
|
||||
installationPath, command, commandArgs, err := parseArgs(args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n\n%s", err, usage)
|
||||
fmt.Fprintf(stderr, "tht: %s\n\n%s", err, usage)
|
||||
return 2
|
||||
}
|
||||
installation, err := config.Load(installationPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n", output.Sanitize(err.Error(), nil))
|
||||
fmt.Fprintf(stderr, "tht: %s\n", output.Sanitize(err.Error(), nil))
|
||||
return 2
|
||||
}
|
||||
secretFiles, err := installation.SecretFiles()
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "thothctl: installation secret declarations could not be read")
|
||||
fmt.Fprintln(stderr, "tht: installation secret declarations could not be read")
|
||||
return 2
|
||||
}
|
||||
secretValues, err := output.SecretValuesFromFiles(secretFiles)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "thothctl: declared secret file could not be read")
|
||||
fmt.Fprintln(stderr, "tht: declared secret file could not be read")
|
||||
return 2
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
return serverOperationFailure(stderr, operationErr, secretValues)
|
||||
}
|
||||
if encodeErr := json.NewEncoder(stdout).Encode(status); encodeErr != nil {
|
||||
fmt.Fprintln(stderr, "thothctl: migration status could not be written")
|
||||
fmt.Fprintln(stderr, "tht: migration status could not be written")
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
@@ -154,7 +154,7 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
removal, operationErr := serverops.Remove(ctx, installation, runner, confirmedIDs)
|
||||
writeRemovalTargets(stdout, installation.ProjectName(), removal.Targets)
|
||||
if errors.Is(operationErr, serverops.ErrConfirmationRequired) {
|
||||
fmt.Fprint(stderr, "thothctl: inspect the exact targets above, then re-run with remove --yes")
|
||||
fmt.Fprint(stderr, "tht: inspect the exact targets above, then re-run with remove --yes")
|
||||
for _, target := range removal.Targets {
|
||||
fmt.Fprintf(stderr, " %s", target.ID)
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func workspaceCommand(ctx context.Context, installation config.Installation, run
|
||||
encoder := json.NewEncoder(stdout)
|
||||
encoder.SetEscapeHTML(false)
|
||||
if encodeErr := encoder.Encode(result); encodeErr != nil {
|
||||
fmt.Fprintln(stderr, "thothctl: workspace result could not be written")
|
||||
fmt.Fprintln(stderr, "tht: workspace result could not be written")
|
||||
return 1
|
||||
}
|
||||
} else {
|
||||
@@ -219,9 +219,9 @@ func workspaceFailure(stderr io.Writer, err error, secretValues []string) int {
|
||||
var operationErr *workspaceops.OperationError
|
||||
if errors.As(err, &operationErr) && operationErr.Detail() != "" {
|
||||
detail := output.SanitizeDetail(operationErr.Detail(), secretValues)
|
||||
fmt.Fprintf(stderr, "thothctl: %s: %s\n", message, detail)
|
||||
fmt.Fprintf(stderr, "tht: %s: %s\n", message, detail)
|
||||
} else {
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n", message)
|
||||
fmt.Fprintf(stderr, "tht: %s\n", message)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
@@ -231,9 +231,9 @@ func serverOperationFailure(stderr io.Writer, err error, secretValues []string)
|
||||
var operationErr *serverops.OperationError
|
||||
if errors.As(err, &operationErr) && operationErr.Detail() != "" {
|
||||
detail := output.SanitizeDetail(operationErr.Detail(), secretValues)
|
||||
fmt.Fprintf(stderr, "thothctl: %s: %s\n", message, detail)
|
||||
fmt.Fprintf(stderr, "tht: %s: %s\n", message, detail)
|
||||
} else {
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n", message)
|
||||
fmt.Fprintf(stderr, "tht: %s\n", message)
|
||||
}
|
||||
if errors.Is(err, serverops.ErrConfirmationRequired) || errors.Is(err, serverops.ErrUnsafeState) {
|
||||
return 2
|
||||
@@ -315,7 +315,7 @@ func piCommand(ctx context.Context, installation config.Installation, runner com
|
||||
if err := pi.Configure(ctx, controlled, defaults); err != nil {
|
||||
return piFailure(stderr, err, secretValues)
|
||||
}
|
||||
fmt.Fprintf(stdout, "Pi defaults applied and read back. Provider credentials remain only in the host file %s (mode 0600). Never pass credentials to thothctl.\n", authFile)
|
||||
fmt.Fprintf(stdout, "Pi defaults applied and read back. Provider credentials remain only in the host file %s (mode 0600). Never pass credentials to tht.\n", authFile)
|
||||
return 0
|
||||
case "restart":
|
||||
request, err := parsePiRestartArgs(
|
||||
@@ -583,7 +583,7 @@ func piFailure(stderr io.Writer, err error, secretValues []string) int {
|
||||
if errors.As(err, &childExit) && childExit.ExitCode() != 0 {
|
||||
code = childExit.ExitCode()
|
||||
}
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n", output.Sanitize(err.Error(), secretValues))
|
||||
fmt.Fprintf(stderr, "tht: %s\n", output.Sanitize(err.Error(), secretValues))
|
||||
return code
|
||||
}
|
||||
|
||||
@@ -605,7 +605,7 @@ func logsArgs(args []string) ([]string, error) {
|
||||
}
|
||||
|
||||
func commandUsageError(stderr io.Writer, message string) int {
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n", message)
|
||||
fmt.Fprintf(stderr, "tht: %s\n", message)
|
||||
return 2
|
||||
}
|
||||
|
||||
@@ -620,7 +620,7 @@ func writeResult(result compose.Result, err error, secretValues []string, stdout
|
||||
return 0
|
||||
}
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
fmt.Fprintln(stderr, "thothctl: Docker is not installed or is not on PATH")
|
||||
fmt.Fprintln(stderr, "tht: Docker is not installed or is not on PATH")
|
||||
}
|
||||
if result.ExitCode != 0 {
|
||||
return result.ExitCode
|
||||
@@ -650,15 +650,15 @@ func doctor(ctx context.Context, installation config.Installation, runner compos
|
||||
}
|
||||
}
|
||||
if err := requireLF(installation.ProjectDirectory); err != nil {
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n", err)
|
||||
fmt.Fprintf(stderr, "tht: %s\n", err)
|
||||
return 1
|
||||
}
|
||||
if err := requireVolumes(renderedConfig); err != nil {
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n", err)
|
||||
fmt.Fprintf(stderr, "tht: %s\n", err)
|
||||
return 1
|
||||
}
|
||||
if err := requireHealthyServices(status); err != nil {
|
||||
fmt.Fprintf(stderr, "thothctl: %s\n", err)
|
||||
fmt.Fprintf(stderr, "tht: %s\n", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintln(stdout, "Doctor checks passed.")
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/pi"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/testsupport"
|
||||
"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/testsupport"
|
||||
)
|
||||
|
||||
func TestInstallationRunnerMapsProfileToSessionInventoryScope(t *testing.T) {
|
||||
@@ -32,6 +32,48 @@ func TestInstallationRunnerMapsProfileToSessionInventoryScope(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootCommandIdentity(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "")
|
||||
fixture.setEnvironment(t)
|
||||
retiredCommand := "thoth" + "ctl"
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
args []string
|
||||
wantCode int
|
||||
wantText string
|
||||
}{
|
||||
{
|
||||
name: "help banner",
|
||||
args: []string{"--help"},
|
||||
wantCode: 0,
|
||||
wantText: "Usage: tht ",
|
||||
},
|
||||
{
|
||||
name: "version path",
|
||||
args: []string{"--installation", fixture.installationPath, "version"},
|
||||
wantCode: 2,
|
||||
wantText: `tht: unknown command "version"`,
|
||||
},
|
||||
{
|
||||
name: "retired command is not an alias",
|
||||
args: []string{"--installation", fixture.installationPath, retiredCommand},
|
||||
wantCode: 2,
|
||||
wantText: `tht: unknown command "` + retiredCommand + `"`,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
if got := run(context.Background(), test.args, &stdout, &stderr); got != test.wantCode {
|
||||
t.Fatalf("run(%v) exit code = %d, want %d", test.args, got, test.wantCode)
|
||||
}
|
||||
if got := stdout.String() + stderr.String(); !strings.Contains(got, test.wantText) {
|
||||
t.Fatalf("run(%v) output = %q, want %q", test.args, got, test.wantText)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -106,8 +148,8 @@ func TestUsageDocumentsClosedConfigureUpdateSourcesRestartAndMaintenanceRecovery
|
||||
}
|
||||
|
||||
func TestParsePiRestartArgs(t *testing.T) {
|
||||
restartPath := "/var/lib/thothctl/restart-state.json"
|
||||
updatePath := "/var/lib/thothctl/update-state.json"
|
||||
restartPath := "/var/lib/tht/restart-state.json"
|
||||
updatePath := "/var/lib/tht/update-state.json"
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
args []string
|
||||
@@ -191,9 +233,9 @@ func TestRunSessionsMigrateRedactsCompleteDetailBeforeDisplayBound(t *testing.T)
|
||||
t.Fatal(err)
|
||||
}
|
||||
fixture.setEnvironment(t, secretPath)
|
||||
t.Setenv("THOTHCTL_FAKE_CONFIG", `{"services":{"core":{"image":"thothii-core:local"},"session-migrate":{"image":"thothii-core:local"}}}`)
|
||||
t.Setenv("THOTHCTL_FAKE_MIGRATION_FAILURE", spec.failure)
|
||||
t.Setenv("THOTHCTL_FAKE_MIGRATION_EXIT", "23")
|
||||
t.Setenv("THT_FAKE_CONFIG", `{"services":{"core":{"image":"thothii-core:local"},"session-migrate":{"image":"thothii-core:local"}}}`)
|
||||
t.Setenv("THT_FAKE_MIGRATION_FAILURE", spec.failure)
|
||||
t.Setenv("THT_FAKE_MIGRATION_EXIT", "23")
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := run(context.Background(), []string{
|
||||
@@ -222,7 +264,7 @@ func TestRunRemoveDisplaysExactInstallationTargetsBeforeConfirmation(t *testing.
|
||||
fixture := newCLIFixture(t, "")
|
||||
fixture.setProfile(t, "server")
|
||||
fixture.setEnvironment(t)
|
||||
t.Setenv("THOTHCTL_FAKE_STOPPED_PS", `[{"ID":"core-id","Name":"exact-core","Service":"core","State":"exited"},{"ID":"front-id","Name":"exact-frontend","Service":"frontend","State":"exited"}]`)
|
||||
t.Setenv("THT_FAKE_STOPPED_PS", `[{"ID":"core-id","Name":"exact-core","Service":"core","State":"exited"},{"ID":"front-id","Name":"exact-frontend","Service":"frontend","State":"exited"}]`)
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
code := run(context.Background(), []string{
|
||||
@@ -258,7 +300,7 @@ func TestRunLogsRedactsAnUnlabelledDeclaredSecret(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fixture.setEnvironment(t, secretPath)
|
||||
t.Setenv("THOTHCTL_FAKE_LOG", "fake Docker log: unlabelled-secret")
|
||||
t.Setenv("THT_FAKE_LOG", "fake Docker log: unlabelled-secret")
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "logs"}, &stdout, &stderr)
|
||||
@@ -298,7 +340,7 @@ func TestRunPiLogsRedactsNestedAuthAndBundleScalars(t *testing.T) {
|
||||
}
|
||||
fixture.setEnvContents(t, "PI_AUTH_FILE="+authFile+"\nTHT_SECRETS_FILE="+bundleFile+"\n")
|
||||
t.Setenv(
|
||||
"THOTHCTL_FAKE_LOG",
|
||||
"THT_FAKE_LOG",
|
||||
"dummy-canary-pi-log-json dummy-canary-pi-log-nested "+
|
||||
"dummy-canary-pi-log-model dummy-canary-pi-log-dwh",
|
||||
)
|
||||
@@ -352,7 +394,7 @@ func TestRunResolvesComposeDotenvCommentsQuotesAndInterpolationForSecretFiles(t
|
||||
"DOUBLE_TOKEN_FILE=\""+doubleQuotedSecret+"\" # Compose comment\n"+
|
||||
"SINGLE_TOKEN_FILE='"+singleQuotedSecret+"' # Compose comment\n"+
|
||||
"INTERPOLATED_TOKEN_SOURCE=\"${SECRET_ROOT}/interpolated\"\n")
|
||||
t.Setenv("THOTHCTL_FAKE_LOG", "inline-secret double-quoted-secret single-quoted-secret interpolated-secret")
|
||||
t.Setenv("THT_FAKE_LOG", "inline-secret double-quoted-secret single-quoted-secret interpolated-secret")
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "logs"}, &stdout, &stderr)
|
||||
@@ -374,9 +416,9 @@ func TestRunRedactsSecretSourceInBothStreams(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fixture.setEnvContents(t, "UNLABELLED_SECRET_SOURCE="+secretPath+"\n")
|
||||
t.Setenv("THOTHCTL_FAKE_LOG", "stdout source-secret")
|
||||
t.Setenv("THOTHCTL_FAKE_FAILURE", "stderr source-secret")
|
||||
t.Setenv("THOTHCTL_FAKE_EXIT", "17")
|
||||
t.Setenv("THT_FAKE_LOG", "stdout source-secret")
|
||||
t.Setenv("THT_FAKE_FAILURE", "stderr source-secret")
|
||||
t.Setenv("THT_FAKE_EXIT", "17")
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "logs"}, &stdout, &stderr)
|
||||
@@ -396,8 +438,8 @@ func TestRunRedactsSecretWhenDoctorFails(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fixture.setEnvContents(t, "DOCTOR_SECRET_FILE="+secretPath+"\n")
|
||||
t.Setenv("THOTHCTL_FAKE_FAIL_ON", "version")
|
||||
t.Setenv("THOTHCTL_FAKE_FAILURE", "doctor saw doctor-secret")
|
||||
t.Setenv("THT_FAKE_FAIL_ON", "version")
|
||||
t.Setenv("THT_FAKE_FAILURE", "doctor saw doctor-secret")
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "doctor"}, &stdout, &stderr)
|
||||
@@ -668,7 +710,7 @@ func TestRunDoctorAcceptsComposeJSONLinesServiceStatus(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "SAFE_VALUE=1\n")
|
||||
fixture.setEnvironment(t)
|
||||
t.Setenv(
|
||||
"THOTHCTL_FAKE_PS",
|
||||
"THT_FAKE_PS",
|
||||
"{\"Service\":\"core\",\"State\":\"running\",\"Health\":\"healthy\"}\n"+
|
||||
"{\"Service\":\"frontend\",\"State\":\"running\",\"Health\":\"healthy\"}",
|
||||
)
|
||||
@@ -687,7 +729,7 @@ func TestRunDoctorAcceptsComposeJSONLinesServiceStatus(t *testing.T) {
|
||||
func TestRunPreservesChildExitCodes(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "SAFE_VALUE=1\n")
|
||||
fixture.setEnvironment(t)
|
||||
t.Setenv("THOTHCTL_FAKE_EXIT", "42")
|
||||
t.Setenv("THT_FAKE_EXIT", "42")
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "update", "--check-only"}, &stdout, &stderr)
|
||||
@@ -757,7 +799,7 @@ func TestRunPiRestartSanitizesSuccessOutput(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fixture.setEnvironment(t, secretPath)
|
||||
t.Setenv("THOTHCTL_FAKE_PI_VERSION", "pi-restart-secret")
|
||||
t.Setenv("THT_FAKE_PI_VERSION", "pi-restart-secret")
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{
|
||||
@@ -886,7 +928,7 @@ func TestRunPiMaintenanceRecoverClearsRestartLifecycleState(t *testing.T) {
|
||||
func TestRunWorkspaceInspectDispatchesThroughTheMaintenanceService(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "SAFE_VALUE=1\n")
|
||||
fixture.setEnvironment(t)
|
||||
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT", `{"schemaVersion":1,"status":"succeeded","code":"ok","workspaceId":"abc","workspaceRevision":"1234567890abcdef1234567890abcdef12345678","descriptorBlob":"sha256:`+strings.Repeat("a", 64)+`","operation":"inspect","completedStages":[]}`)
|
||||
t.Setenv("THT_FAKE_WORKSPACE_RESULT", `{"schemaVersion":1,"status":"succeeded","code":"ok","workspaceId":"abc","workspaceRevision":"1234567890abcdef1234567890abcdef12345678","descriptorBlob":"sha256:`+strings.Repeat("a", 64)+`","operation":"inspect","completedStages":[]}`)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "abc", "--json"}, &stdout, &stderr)
|
||||
@@ -904,7 +946,7 @@ func TestRunWorkspaceInspectDispatchesThroughTheMaintenanceService(t *testing.T)
|
||||
func TestRunWorkspaceBlockedResultsExitThreeAndRenderHumanOutput(t *testing.T) {
|
||||
fixture := newCLIFixture(t, "SAFE_VALUE=1\n")
|
||||
fixture.setEnvironment(t)
|
||||
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT", `{"schemaVersion":1,"status":"blocked","code":"manual_review_required","workspaceId":"abc","workspaceRevision":"1234567890abcdef1234567890abcdef12345678","descriptorBlob":"sha256:`+strings.Repeat("b", 64)+`","operation":"schema-suggest-fks","completedStages":["dwh"],"warnings":["review required"]}`)
|
||||
t.Setenv("THT_FAKE_WORKSPACE_RESULT", `{"schemaVersion":1,"status":"blocked","code":"manual_review_required","workspaceId":"abc","workspaceRevision":"1234567890abcdef1234567890abcdef12345678","descriptorBlob":"sha256:`+strings.Repeat("b", 64)+`","operation":"schema-suggest-fks","completedStages":["dwh"],"warnings":["review required"]}`)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "schema", "suggest-fks", "--workspace", "abc"}, &stdout, &stderr)
|
||||
@@ -1012,8 +1054,8 @@ func TestRunPiStatusPreservesDockerExitCodeAndRedactsDiagnostics(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fixture.setEnvironment(t, secretPath)
|
||||
t.Setenv("THOTHCTL_FAKE_FAIL_ON", "version")
|
||||
t.Setenv("THOTHCTL_FAKE_FAILURE", "pi-status-secret")
|
||||
t.Setenv("THT_FAKE_FAIL_ON", "version")
|
||||
t.Setenv("THT_FAKE_FAILURE", "pi-status-secret")
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
exitCode := run(context.Background(), []string{"--installation", fixture.installationPath, "pi", "status"}, &stdout, &stderr)
|
||||
@@ -1042,7 +1084,7 @@ func newCLIFixture(t *testing.T, envTemplate string) cliFixture {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := os.MkdirTemp(temporaryRoot, "thothctl-test-")
|
||||
root, err := os.MkdirTemp(temporaryRoot, "tht-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1068,30 +1110,30 @@ func newCLIFixture(t *testing.T, envTemplate string) cliFixture {
|
||||
}
|
||||
argsFile := filepath.Join(root, "docker-args")
|
||||
fakeDocker := `#!/bin/sh
|
||||
printf '%s\n' "$@" >> "$THOTHCTL_FAKE_ARGS"
|
||||
printf '%s\n' -- >> "$THOTHCTL_FAKE_ARGS"
|
||||
printf '%s\n' "$@" >> "$THT_FAKE_ARGS"
|
||||
printf '%s\n' -- >> "$THT_FAKE_ARGS"
|
||||
case " $* " in
|
||||
*" ps --all --format json core frontend "*) printf '%s\n' "${THOTHCTL_FAKE_STOPPED_PS:-[]}" ;;
|
||||
*" ps --all --format json core frontend "*) printf '%s\n' "${THT_FAKE_STOPPED_PS:-[]}" ;;
|
||||
*" config --format json "*)
|
||||
if [ -n "${THOTHCTL_FAKE_CONFIG:-}" ]; then
|
||||
printf '%s\n' "$THOTHCTL_FAKE_CONFIG"
|
||||
if [ -n "${THT_FAKE_CONFIG:-}" ]; then
|
||||
printf '%s\n' "$THT_FAKE_CONFIG"
|
||||
else
|
||||
printf '%s\n' '{"volumes":{"settings":{}},"services":{"core":{"image":"thothii-core:local","environment":{"THT_LLM_URL":"https://llm.example.invalid"}}}}'
|
||||
fi ;;
|
||||
*" run --rm --no-deps --no-TTY session-migrate "*)
|
||||
if [ "${THOTHCTL_FAKE_MIGRATION_EXIT:-0}" -ne 0 ]; then
|
||||
printf '%s\n' "$THOTHCTL_FAKE_MIGRATION_FAILURE" >&2
|
||||
exit "$THOTHCTL_FAKE_MIGRATION_EXIT"
|
||||
if [ "${THT_FAKE_MIGRATION_EXIT:-0}" -ne 0 ]; then
|
||||
printf '%s\n' "$THT_FAKE_MIGRATION_FAILURE" >&2
|
||||
exit "$THT_FAKE_MIGRATION_EXIT"
|
||||
fi
|
||||
printf '%s\n' '{"applied":[],"drifted":[],"pending":[]}' ;;
|
||||
*" ps --format json "*) printf '%s\n' "$THOTHCTL_FAKE_PS" ;;
|
||||
*" ps --format json "*) printf '%s\n' "$THT_FAKE_PS" ;;
|
||||
*" ps -q core "*) printf '%s\n' 'core-id' ;;
|
||||
*" image inspect --format {{.Id}} "*) printf '%s\n' "${THOTHCTL_FAKE_IMAGE_ID:-sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}" ;;
|
||||
*"inspect --format {{.Image}} core-id"*) printf '%s\n' "${THOTHCTL_FAKE_IMAGE_ID:-sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}" ;;
|
||||
*" image inspect --format {{.Id}} "*) printf '%s\n' "${THT_FAKE_IMAGE_ID:-sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}" ;;
|
||||
*"inspect --format {{.Image}} core-id"*) printf '%s\n' "${THT_FAKE_IMAGE_ID:-sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}" ;;
|
||||
*"inspect --format {{json .Mounts}} core-id"*) printf '%s\n' '[{"Type":"volume","Name":"settings","Source":"settings","Destination":"/home/thoth/.pi","RW":true}]' ;;
|
||||
*"io.thothii.pi.version"*) printf '%s\n' "${THOTHCTL_FAKE_PI_VERSION:-0.80.3}" ;;
|
||||
*"PI_VERSION"*) printf '%s\n' "${THOTHCTL_FAKE_PI_VERSION:-0.80.3}" ;;
|
||||
*" pi --version "*) printf '%s\n' "${THOTHCTL_FAKE_PI_VERSION:-0.80.3}" ;;
|
||||
*"io.thothii.pi.version"*) printf '%s\n' "${THT_FAKE_PI_VERSION:-0.80.3}" ;;
|
||||
*"PI_VERSION"*) printf '%s\n' "${THT_FAKE_PI_VERSION:-0.80.3}" ;;
|
||||
*" pi --version "*) printf '%s\n' "${THT_FAKE_PI_VERSION:-0.80.3}" ;;
|
||||
*"/pi-management/options "*) printf '%s\n' '{"providers":["provider"],"models":[{"provider":"provider","id":"model"}],"reasoning":["low","medium","high"]}' ;;
|
||||
*"settings-cli.js --snapshot"*) printf '%s\n' '{"exists":false,"rawBase64":""}' ;;
|
||||
*"/settings "*) printf '%s\n' '{"provider":"provider","model":"model","thinking":"medium"}' ;;
|
||||
@@ -1100,17 +1142,17 @@ case " $* " in
|
||||
*"/internal/maintenance/activate "*) printf '%s\n' '{"active":true,"admissions":0}' ;;
|
||||
*"/internal/maintenance/deactivate "*) printf '%s\n' '{"active":false,"admissions":0}' ;;
|
||||
*"/internal/maintenance/status "*) printf '%s\n' '{"active":true,"admissions":0}' ;;
|
||||
*" logs "*) printf '%s\n' "$THOTHCTL_FAKE_LOG" ;;
|
||||
*" run --rm --no-deps --no-TTY "*" workspace-maintenance "*) printf '%s\n' "$THOTHCTL_FAKE_WORKSPACE_RESULT" ;;
|
||||
*" logs "*) printf '%s\n' "$THT_FAKE_LOG" ;;
|
||||
*" run --rm --no-deps --no-TTY "*" workspace-maintenance "*) printf '%s\n' "$THT_FAKE_WORKSPACE_RESULT" ;;
|
||||
esac
|
||||
if [ "${THOTHCTL_FAKE_FAIL_ON:-}" = "version" ]; then
|
||||
printf '%s\n' "${THOTHCTL_FAKE_FAILURE:-fake Docker failure}" >&2
|
||||
if [ "${THT_FAKE_FAIL_ON:-}" = "version" ]; then
|
||||
printf '%s\n' "${THT_FAKE_FAILURE:-fake Docker failure}" >&2
|
||||
exit 41
|
||||
fi
|
||||
if [ "${THOTHCTL_FAKE_EXIT:-0}" -ne 0 ]; then
|
||||
printf '%s\n' "${THOTHCTL_FAKE_FAILURE:-fake Docker failure}" >&2
|
||||
if [ "${THT_FAKE_EXIT:-0}" -ne 0 ]; then
|
||||
printf '%s\n' "${THT_FAKE_FAILURE:-fake Docker failure}" >&2
|
||||
fi
|
||||
exit "${THOTHCTL_FAKE_EXIT:-0}"
|
||||
exit "${THT_FAKE_EXIT:-0}"
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(pathDirectory, "docker"), []byte(fakeDocker), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -1133,19 +1175,19 @@ func (f cliFixture) setEnvContents(t *testing.T, env string) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("PATH", f.pathDirectory)
|
||||
t.Setenv("THOTHCTL_FAKE_ARGS", f.argsFile)
|
||||
t.Setenv("THOTHCTL_FAKE_EXIT", "0")
|
||||
t.Setenv("THOTHCTL_FAKE_LOG", "")
|
||||
t.Setenv("THOTHCTL_FAKE_FAILURE", "")
|
||||
t.Setenv("THOTHCTL_FAKE_FAIL_ON", "")
|
||||
t.Setenv("THOTHCTL_FAKE_STOPPED_PS", "[]")
|
||||
t.Setenv("THOTHCTL_FAKE_PS", `[{"Service":"core","State":"running","Health":"healthy"},{"Service":"frontend","State":"running","Health":"healthy"}]`)
|
||||
t.Setenv("THOTHCTL_FAKE_CONFIG", "")
|
||||
t.Setenv("THOTHCTL_FAKE_MIGRATION_FAILURE", "")
|
||||
t.Setenv("THOTHCTL_FAKE_MIGRATION_EXIT", "0")
|
||||
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT", "")
|
||||
t.Setenv("THOTHCTL_FAKE_IMAGE_ID", "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
|
||||
t.Setenv("THOTHCTL_FAKE_PI_VERSION", "0.80.3")
|
||||
t.Setenv("THT_FAKE_ARGS", f.argsFile)
|
||||
t.Setenv("THT_FAKE_EXIT", "0")
|
||||
t.Setenv("THT_FAKE_LOG", "")
|
||||
t.Setenv("THT_FAKE_FAILURE", "")
|
||||
t.Setenv("THT_FAKE_FAIL_ON", "")
|
||||
t.Setenv("THT_FAKE_STOPPED_PS", "[]")
|
||||
t.Setenv("THT_FAKE_PS", `[{"Service":"core","State":"running","Health":"healthy"},{"Service":"frontend","State":"running","Health":"healthy"}]`)
|
||||
t.Setenv("THT_FAKE_CONFIG", "")
|
||||
t.Setenv("THT_FAKE_MIGRATION_FAILURE", "")
|
||||
t.Setenv("THT_FAKE_MIGRATION_EXIT", "0")
|
||||
t.Setenv("THT_FAKE_WORKSPACE_RESULT", "")
|
||||
t.Setenv("THT_FAKE_IMAGE_ID", "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")
|
||||
t.Setenv("THT_FAKE_PI_VERSION", "0.80.3")
|
||||
}
|
||||
|
||||
func (f cliFixture) setProfile(t *testing.T, profile string) {
|
||||
@@ -1,4 +1,4 @@
|
||||
module github.com/aritmolab/thothii/tools/thothctl
|
||||
module github.com/aritmolab/thothii/tools/tht
|
||||
|
||||
go 1.26.0
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
// Package config loads the non-secret, local installation descriptor used by thothctl.
|
||||
// Package config loads the non-secret, local installation descriptor used by tht.
|
||||
package config
|
||||
|
||||
import (
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/safeio"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
|
||||
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -235,7 +235,7 @@ func (i Installation) ComposeFiles() []string {
|
||||
// ControlDirectory contains state that is private to one installation descriptor, even when
|
||||
// multiple installations intentionally share one source checkout.
|
||||
func (i Installation) ControlDirectory() string {
|
||||
return filepath.Join(i.ProjectDirectory, ".thothctl", i.ProjectName())
|
||||
return filepath.Join(i.ProjectDirectory, ".tht", i.ProjectName())
|
||||
}
|
||||
|
||||
func (i Installation) CurrentImageOverridePath() string {
|
||||
@@ -284,7 +284,7 @@ func (i Installation) composeArgs(files []string, command ...string) []string {
|
||||
|
||||
// SecretFiles returns canonical local secret paths declared through *_FILE or *_SOURCE variables.
|
||||
// Compose's dotenv parser resolves comments, quotes, escapes, and interpolation. Unsupported or
|
||||
// unresolved source interpolation is rejected before thothctl invokes Docker.
|
||||
// unresolved source interpolation is rejected before tht invokes Docker.
|
||||
func (i Installation) SecretFiles() ([]string, error) {
|
||||
contents, err := safeio.ReadCanonicalRegular(i.EnvFile, maxEnvironmentFileBytes)
|
||||
if err != nil {
|
||||
+3
-3
@@ -218,8 +218,8 @@ func TestInstallationControlPathsAreIsolatedForDescriptorsSharingOneCheckout(t *
|
||||
t.Fatalf("shared-checkout installations reused %q", first.CurrentImageOverridePath())
|
||||
}
|
||||
for _, installation := range []Installation{first, second} {
|
||||
if filepath.Dir(filepath.Dir(installation.CurrentImageOverridePath())) != filepath.Join(projectDirectory, ".thothctl") {
|
||||
t.Fatalf("current-image path %q is not installation-specific under .thothctl", installation.CurrentImageOverridePath())
|
||||
if filepath.Dir(filepath.Dir(installation.CurrentImageOverridePath())) != filepath.Join(projectDirectory, ".tht") {
|
||||
t.Fatalf("current-image path %q is not installation-specific under .tht", installation.CurrentImageOverridePath())
|
||||
}
|
||||
if filepath.Dir(installation.UpdateStatePath()) != filepath.Dir(installation.CurrentImageOverridePath()) {
|
||||
t.Fatalf("state %q and selector %q do not share one installation control directory", installation.UpdateStatePath(), installation.CurrentImageOverridePath())
|
||||
@@ -246,7 +246,7 @@ func writeInstallation(t *testing.T, profile string) (string, string, string, st
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
physicalRoot, err := os.MkdirTemp(temporaryRoot, "thothctl-config-")
|
||||
physicalRoot, err := os.MkdirTemp(temporaryRoot, "tht-config-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/safeio"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
|
||||
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||
)
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@ func physicalTempDir(t *testing.T) string {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
directory, err := os.MkdirTemp(root, "thothctl-output-test-")
|
||||
directory, err := os.MkdirTemp(root, "tht-output-test-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
)
|
||||
|
||||
var choicePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._/-]{0,127}$`)
|
||||
@@ -40,9 +40,9 @@ type settingsFileSnapshot struct {
|
||||
}
|
||||
|
||||
var internalIdentityHeaders = []string{
|
||||
"-H", "x-thoth-principal-issuer: thothctl",
|
||||
"-H", "x-thoth-principal-subject: thothctl-maintenance",
|
||||
"-H", "x-thoth-principal-display-name: Thothctl maintenance",
|
||||
"-H", "x-thoth-principal-issuer: tht",
|
||||
"-H", "x-thoth-principal-subject: tht-maintenance",
|
||||
"-H", "x-thoth-principal-display-name: Tht maintenance",
|
||||
"-H", "x-thoth-is-admin: 1",
|
||||
}
|
||||
|
||||
@@ -221,7 +221,7 @@ func readEffectiveSettings(ctx context.Context, runner Runner) ([]byte, error) {
|
||||
return canonical, nil
|
||||
}
|
||||
|
||||
// Runner is the narrow, shell-free command boundary shared with thothctl.
|
||||
// Runner is the narrow, shell-free command boundary shared with tht.
|
||||
type Runner interface {
|
||||
Run(context.Context, []string, io.Reader) (compose.Result, error)
|
||||
}
|
||||
+5
-5
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
)
|
||||
|
||||
func TestDoctorRequiresExternalEndpointAuthPiStateAndHealth(t *testing.T) {
|
||||
@@ -218,7 +218,7 @@ func TestConfigureCompensationRestoresAbsentAndExactEmptyPriorFiles(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
// Catches thothctl reading the legacy public model route instead of the admin-only closed Pi
|
||||
// Catches tht reading the legacy public model route instead of the admin-only closed Pi
|
||||
// Management choices before it writes shared installation defaults.
|
||||
func TestConfigureLoadsDedicatedClosedOptionsWritesRealCoreSettingsAndUsesUpstreamIdentity(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
@@ -227,7 +227,7 @@ func TestConfigureLoadsDedicatedClosedOptionsWritesRealCoreSettingsAndUsesUpstre
|
||||
}
|
||||
assertCalled(t, fake.calls, "/pi-management/options")
|
||||
assertCalled(t, fake.calls, "node /app/backend/dist/settings/settings-cli.js --provider provider --model model --thinking medium")
|
||||
assertCalled(t, fake.calls, "x-thoth-principal-subject: thothctl-maintenance")
|
||||
assertCalled(t, fake.calls, "x-thoth-principal-subject: tht-maintenance")
|
||||
if got := strings.Join(fake.calls, "\n"); strings.Contains(got, "pi-defaults.json") || strings.Contains(got, "secret") {
|
||||
t.Fatalf("commands=%q", got)
|
||||
}
|
||||
@@ -243,7 +243,7 @@ func TestTestUsesDedicatedSmokeEndpointAndIndependentImageVersionProbe(t *testin
|
||||
if err := Test(context.Background(), fake); err != nil {
|
||||
t.Fatalf("Test() error = %v", err)
|
||||
}
|
||||
for _, command := range []string{"pi --version", "/pi-management/test", "x-thoth-principal-subject: thothctl-maintenance"} {
|
||||
for _, command := range []string{"pi --version", "/pi-management/test", "x-thoth-principal-subject: tht-maintenance"} {
|
||||
assertCalled(t, fake.calls, command)
|
||||
}
|
||||
for _, legacy := range []string{"/health", "/models", "/settings"} {
|
||||
@@ -271,7 +271,7 @@ func TestTestRequiresDedicatedSmokeEndpointToReportReady(t *testing.T) {
|
||||
assertCalled(t, fake.calls, "pi --version")
|
||||
}
|
||||
|
||||
// Catches thothctl accepting a reasoning level that the backend did not publish as a closed
|
||||
// Catches tht accepting a reasoning level that the backend did not publish as a closed
|
||||
// installation option, which would bypass the Pi Management validation surface.
|
||||
func TestConfigureRejectsReasoningOutsideDedicatedClosedOptions(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
@@ -93,7 +93,7 @@ func TestRestartPinsCapturedImageWhenConfiguredTagMovesBeforeRecreate(t *testing
|
||||
if result.Version != "0.80.3" || fake.currentImage != "sha256:old" {
|
||||
t.Fatalf("restart result=%+v image=%q; want captured 0.80.3 / sha256:old", result, fake.currentImage)
|
||||
}
|
||||
assertCalled(t, fake.calls, "image tag sha256:old thothii-core:thothctl-")
|
||||
assertCalled(t, fake.calls, "image tag sha256:old thothii-core:tht-")
|
||||
assertCalled(t, fake.calls, "pi-lifecycle-")
|
||||
if matches, globErr := filepath.Glob(filepath.Join(dir, "pi-lifecycle-*.yaml")); globErr != nil || len(matches) != 0 {
|
||||
t.Fatalf("successful restart overrides = %v, error = %v; want safe cleanup", matches, globErr)
|
||||
@@ -285,7 +285,7 @@ func TestRecoverLifecycleMaintenanceVerifiesAndClearsRestartState(t *testing.T)
|
||||
delete(fake.tags, restartState.Previous.Reference)
|
||||
fake.tags[fake.configuredImage] = "sha256:moved-before-recovery"
|
||||
candidate := previous
|
||||
candidate.Reference = "thothii-core:thothctl-recover-candidate"
|
||||
candidate.Reference = "thothii-core:tht-recover-candidate"
|
||||
writeStateForTest(t, updateStatePath, State{
|
||||
Transaction: "update-recovery",
|
||||
Phase: PhasePromoting,
|
||||
@@ -40,15 +40,15 @@ func TestUpdateAndRestartStatePathsShareOneLifecycleLock(t *testing.T) {
|
||||
|
||||
func TestAdvisoryLockCrashReleasesAndReacquires(t *testing.T) {
|
||||
statePath := filepath.Join(t.TempDir(), "update-state.json")
|
||||
if os.Getenv("THOTHCTL_LOCK_CRASH_HELPER") == "1" {
|
||||
lock, err := acquireLock(os.Getenv("THOTHCTL_LOCK_STATE"))
|
||||
if os.Getenv("THT_LOCK_CRASH_HELPER") == "1" {
|
||||
lock, err := acquireLock(os.Getenv("THT_LOCK_STATE"))
|
||||
if err != nil || lock == nil {
|
||||
os.Exit(23)
|
||||
}
|
||||
os.Exit(0) // Deliberately bypass Release: the OS must release ownership.
|
||||
}
|
||||
command := exec.Command(os.Args[0], "-test.run=^TestAdvisoryLockCrashReleasesAndReacquires$")
|
||||
command.Env = append(os.Environ(), "THOTHCTL_LOCK_CRASH_HELPER=1", "THOTHCTL_LOCK_STATE="+statePath)
|
||||
command.Env = append(os.Environ(), "THT_LOCK_CRASH_HELPER=1", "THT_LOCK_STATE="+statePath)
|
||||
if output, err := command.CombinedOutput(); err != nil {
|
||||
t.Fatalf("crash helper failed: %v: %s", err, output)
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
"github.com/distribution/reference"
|
||||
)
|
||||
|
||||
@@ -748,7 +748,7 @@ func lifecycleTransaction(statePath string) string {
|
||||
}
|
||||
|
||||
func lifecycleImageTag(transaction, role string) string {
|
||||
return "thothii-core:thothctl-" + transaction + "-" + role
|
||||
return "thothii-core:tht-" + transaction + "-" + role
|
||||
}
|
||||
|
||||
func lifecycleOverridePath(statePath, transaction string) string {
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
)
|
||||
|
||||
func TestActiveSessionsParsesAuthenticatedBackendBareArrayFixture(t *testing.T) {
|
||||
@@ -58,7 +58,7 @@ func TestUpdateBuildsPinnedVersionRecreatesOnlyCoreAndPersistsRecoveryState(t *t
|
||||
fake := newFakeRunner()
|
||||
dir := t.TempDir()
|
||||
result, err := Update(context.Background(), fake, Request{
|
||||
StatePath: filepath.Join(dir, ".thothctl", "update-state.json"),
|
||||
StatePath: filepath.Join(dir, ".tht", "update-state.json"),
|
||||
Version: "0.81.0",
|
||||
Source: BuildSource,
|
||||
Confirm: true,
|
||||
@@ -85,11 +85,11 @@ func TestUpdateBuildsPinnedVersionRecreatesOnlyCoreAndPersistsRecoveryState(t *t
|
||||
|
||||
func TestUpdateUsesATransactionScopedComposeOverrideWithoutMutatingTheConfiguredImage(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
statePath := filepath.Join(t.TempDir(), ".thothctl", "update-state.json")
|
||||
statePath := filepath.Join(t.TempDir(), ".tht", "update-state.json")
|
||||
if _, err := Update(context.Background(), fake, Request{StatePath: statePath, Version: "0.81.0", Source: BuildSource, Confirm: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fake.buildReference == "" || fake.buildReference == fake.configuredImage || !strings.Contains(fake.buildReference, "thothctl-") {
|
||||
if fake.buildReference == "" || fake.buildReference == fake.configuredImage || !strings.Contains(fake.buildReference, "tht-") {
|
||||
t.Fatalf("build reference = %q, configured = %q; want unique lifecycle tag", fake.buildReference, fake.configuredImage)
|
||||
}
|
||||
assertNotCalled(t, fake.calls, "image tag sha256:old "+fake.configuredImage)
|
||||
@@ -103,7 +103,7 @@ func TestUpdateUsesATransactionScopedComposeOverrideWithoutMutatingTheConfigured
|
||||
|
||||
func TestSuccessfulUpdateAndRollbackRemainSelectedOnFreshRecreate(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
statePath := filepath.Join(t.TempDir(), ".thothctl", "update-state.json")
|
||||
statePath := filepath.Join(t.TempDir(), ".tht", "update-state.json")
|
||||
if _, err := Update(context.Background(), fake, Request{StatePath: statePath, Version: "0.81.0", Source: BuildSource, Confirm: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -205,7 +205,7 @@ func TestDigestPinnedConfiguredImageIsNeverUsedAsARollbackTagTarget(t *testing.T
|
||||
fake := newFakeRunner()
|
||||
fake.configuredImage = "registry.example.invalid/core@sha256:" + strings.Repeat("b", 64)
|
||||
fake.tags = map[string]string{fake.configuredImage: "sha256:old"}
|
||||
statePath := filepath.Join(t.TempDir(), ".thothctl", "state.json")
|
||||
statePath := filepath.Join(t.TempDir(), ".tht", "state.json")
|
||||
if _, err := Update(context.Background(), fake, Request{StatePath: statePath, Version: "0.81.0", Source: BuildSource, Confirm: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -343,7 +343,7 @@ func TestCompensationReactivatesMaintenanceAndRescansBeforeRollback(t *testing.T
|
||||
func TestAutomaticRollbackSurvivesADeadCandidateCore(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
fake.fail = "dead-candidate"
|
||||
statePath := filepath.Join(t.TempDir(), ".thothctl", "update-state.json")
|
||||
statePath := filepath.Join(t.TempDir(), ".tht", "update-state.json")
|
||||
|
||||
result, err := Update(context.Background(), fake, Request{
|
||||
StatePath: statePath,
|
||||
@@ -367,9 +367,9 @@ func TestAutomaticRollbackSurvivesADeadCandidateCore(t *testing.T) {
|
||||
|
||||
func TestManualRollbackSurvivesADeadCandidateCore(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
statePath := filepath.Join(t.TempDir(), ".thothctl", "update-state.json")
|
||||
statePath := filepath.Join(t.TempDir(), ".tht", "update-state.json")
|
||||
previous := stateImageForTest(t, fake)
|
||||
previous.Reference = "thothii-core:thothctl-dead-candidate-previous"
|
||||
previous.Reference = "thothii-core:tht-dead-candidate-previous"
|
||||
fake.tags[previous.Reference] = previous.ID
|
||||
writeStateForTest(t, statePath, State{
|
||||
Transaction: "dead-candidate",
|
||||
@@ -405,7 +405,7 @@ func TestUpdatePullsOnlyDigestPinnedSource(t *testing.T) {
|
||||
t.Fatalf("Update() pull error = %v", err)
|
||||
}
|
||||
assertCalled(t, fake.calls, "pull "+digest)
|
||||
assertCalled(t, fake.calls, "image tag "+digest+" thothii-core:thothctl-")
|
||||
assertCalled(t, fake.calls, "image tag "+digest+" thothii-core:tht-")
|
||||
assertNotCalled(t, fake.calls, "image tag "+digest+" thothii-core:local")
|
||||
|
||||
fake = newFakeRunner()
|
||||
@@ -442,7 +442,7 @@ func TestUpdateRollsBackAfterPostRecreateFailures(t *testing.T) {
|
||||
if result.Phase != PhaseRolledBack {
|
||||
t.Fatalf("phase = %q, want %q", result.Phase, PhaseRolledBack)
|
||||
}
|
||||
assertCalled(t, fake.calls, "image tag sha256:old thothii-core:thothctl-")
|
||||
assertCalled(t, fake.calls, "image tag sha256:old thothii-core:tht-")
|
||||
assertNotCalled(t, fake.calls, "image tag sha256:old thothii-core:local")
|
||||
assertCalled(t, fake.calls, "up --detach --wait --wait-timeout 45 --no-deps --force-recreate core")
|
||||
if got := string(readStateBytes(t, statePath)); !strings.Contains(got, `"phase": "rolled_back"`) {
|
||||
@@ -613,11 +613,11 @@ func TestRecoverMaintenanceCompletesAnInterruptedDurablePromotion(t *testing.T)
|
||||
fake.version = "0.81.0"
|
||||
fake.expectedVersion = "0.81.0"
|
||||
fake.labelVersion = "0.81.0"
|
||||
statePath := filepath.Join(t.TempDir(), ".thothctl", "update-state.json")
|
||||
statePath := filepath.Join(t.TempDir(), ".tht", "update-state.json")
|
||||
previous := stateImageForTest(t, newFakeRunner())
|
||||
candidate := previous
|
||||
candidate.ID = "sha256:candidate"
|
||||
candidate.Reference = "thothii-core:thothctl-recover-candidate"
|
||||
candidate.Reference = "thothii-core:tht-recover-candidate"
|
||||
fake.tags[candidate.Reference] = candidate.ID
|
||||
state := State{
|
||||
Transaction: "promotion-recovery",
|
||||
@@ -647,7 +647,7 @@ func TestRollbackFinalStateWriteFailureKeepsMaintenanceAndOverrideForRecovery(t
|
||||
fake := newFakeRunner()
|
||||
statePath := filepath.Join(t.TempDir(), "state.json")
|
||||
previous := stateImageForTest(t, fake)
|
||||
previous.Reference = "thothii-core:thothctl-rollback-test-previous"
|
||||
previous.Reference = "thothii-core:tht-rollback-test-previous"
|
||||
fake.tags[previous.Reference] = previous.ID
|
||||
writeStateForTest(t, statePath, State{
|
||||
Transaction: "rollback-test",
|
||||
@@ -700,7 +700,7 @@ func TestCandidateBuildAndPullFailuresRemainPreMutationAndNeverRecreateCore(t *t
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
fake := newFakeRunner()
|
||||
fake.fail = testCase.failure
|
||||
statePath := filepath.Join(t.TempDir(), ".thothctl", "update-state.json")
|
||||
statePath := filepath.Join(t.TempDir(), ".tht", "update-state.json")
|
||||
result, err := Update(context.Background(), fake, Request{StatePath: statePath, Version: "0.81.0", Source: testCase.source, Image: testCase.image, Confirm: true})
|
||||
if err == nil {
|
||||
t.Fatal("Update() error = nil, want preparation failure")
|
||||
@@ -727,7 +727,7 @@ func TestSuccessfulCompensationPreservesTheOriginalTypedCause(t *testing.T) {
|
||||
for _, cause := range []error{ErrActiveSessions, ErrInterruptedUpdate} {
|
||||
fake := newFakeRunner()
|
||||
fake.maintenance = true
|
||||
statePath := filepath.Join(t.TempDir(), ".thothctl", "update-state.json")
|
||||
statePath := filepath.Join(t.TempDir(), ".tht", "update-state.json")
|
||||
state := State{Transaction: "typed-cause", Phase: PhaseRecreated, MutationStarted: true, Previous: stateImageForTest(t, fake)}
|
||||
result, err, clear := compensate(context.Background(), fake, statePath, lifecycleOverridePath(statePath, state.Transaction), state, cause, defaultLifecycleHooks)
|
||||
if result.Phase != PhaseRolledBack || !clear {
|
||||
@@ -780,7 +780,7 @@ func TestRollbackRestoresInterruptedOrPreviouslyRecordedState(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
previous.ConfigurationSHA = configured.ConfigurationSHA
|
||||
previous.Reference = "thothii-core:thothctl-test-previous"
|
||||
previous.Reference = "thothii-core:tht-test-previous"
|
||||
fake.tags[previous.Reference] = previous.ID
|
||||
writeStateForTest(t, statePath, State{Version: 1, Phase: PhaseRecreated, Previous: previous})
|
||||
result, err := Rollback(context.Background(), fake, statePath, pairedRestartStatePath(statePath), true)
|
||||
@@ -790,7 +790,7 @@ func TestRollbackRestoresInterruptedOrPreviouslyRecordedState(t *testing.T) {
|
||||
if result.Phase != PhaseRolledBack {
|
||||
t.Fatalf("phase = %q, want %q", result.Phase, PhaseRolledBack)
|
||||
}
|
||||
assertCalled(t, fake.calls, "image tag sha256:old thothii-core:thothctl-test-previous")
|
||||
assertCalled(t, fake.calls, "image tag sha256:old thothii-core:tht-test-previous")
|
||||
assertCalled(t, fake.calls, "up --detach --wait --wait-timeout 45 --no-deps --force-recreate core")
|
||||
}
|
||||
|
||||
@@ -1349,6 +1349,6 @@ func stateImageForTest(t *testing.T, fake *fakeRunner) Image {
|
||||
t.Fatal(err)
|
||||
}
|
||||
image.ConfigurationSHA = configured.ConfigurationSHA
|
||||
image.Reference = "thothii-core:thothctl-test-previous"
|
||||
image.Reference = "thothii-core:tht-test-previous"
|
||||
return image
|
||||
}
|
||||
+4
-4
@@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/testsupport"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/testsupport"
|
||||
)
|
||||
|
||||
func TestReadCanonicalRegularRejectsFinalAndParentSymlinks(t *testing.T) {
|
||||
@@ -14,7 +14,7 @@ func TestReadCanonicalRegularRejectsFinalAndParentSymlinks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := os.MkdirTemp(temporaryRoot, "thothctl-safeio-")
|
||||
root, err := os.MkdirTemp(temporaryRoot, "tht-safeio-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func TestReadCanonicalUTF8RejectsNonUTF8AndHardlinks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := os.MkdirTemp(temporaryRoot, "thothctl-safeio-")
|
||||
root, err := os.MkdirTemp(temporaryRoot, "tht-safeio-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func TestWriteCanonicalNewFileRejectsExistingTargets(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := os.MkdirTemp(temporaryRoot, "thothctl-safeio-")
|
||||
root, err := os.MkdirTemp(temporaryRoot, "tht-safeio-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
+1
-1
@@ -41,7 +41,7 @@ func ReadCanonicalRegular(path string, maximum int64) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
file := os.NewFile(uintptr(descriptor), "thothctl-safeio")
|
||||
file := os.NewFile(uintptr(descriptor), "tht-safeio")
|
||||
if file == nil {
|
||||
unix.Close(descriptor)
|
||||
return nil, ErrUnsafeFile
|
||||
+1
-1
@@ -16,7 +16,7 @@ func TestReadCanonicalRegularRejectsNamedPipeWithoutBlocking(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := os.MkdirTemp(temporaryRoot, "thothctl-safeio-")
|
||||
root, err := os.MkdirTemp(temporaryRoot, "tht-safeio-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
+1
-1
@@ -47,7 +47,7 @@ func ReadCanonicalRegular(path string, maximum int64) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
file := os.NewFile(uintptr(handle), "thothctl-safeio")
|
||||
file := os.NewFile(uintptr(handle), "tht-safeio")
|
||||
if file == nil {
|
||||
windows.CloseHandle(handle)
|
||||
return nil, ErrUnsafeFile
|
||||
+2
-2
@@ -12,8 +12,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
)
|
||||
|
||||
var (
|
||||
+3
-3
@@ -11,8 +11,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
)
|
||||
|
||||
type fakeRunner struct {
|
||||
@@ -298,7 +298,7 @@ func testInstallation(t *testing.T) config.Installation {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := os.MkdirTemp(temporaryRoot, "thothctl-serverops-")
|
||||
root, err := os.MkdirTemp(temporaryRoot, "tht-serverops-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Package testsupport provides portable helpers shared by thothctl tests.
|
||||
// Package testsupport provides portable helpers shared by tht tests.
|
||||
package testsupport
|
||||
|
||||
import (
|
||||
+3
-3
@@ -15,9 +15,9 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/safeio"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
|
||||
)
|
||||
|
||||
var (
|
||||
+3
-3
@@ -12,8 +12,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/thothctl/internal/config"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/compose"
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
||||
)
|
||||
|
||||
type fakeRunner struct {
|
||||
@@ -167,7 +167,7 @@ func testInstallation(t *testing.T) config.Installation {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
root, err := os.MkdirTemp(temporaryRoot, "thothctl-workspaceops-")
|
||||
root, err := os.MkdirTemp(temporaryRoot, "tht-workspaceops-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
Reference in New Issue
Block a user