feat: publish verifiable proxyctl v1
This commit is contained in:
+22
-4
@@ -27,6 +27,9 @@ import (
|
||||
"proxyctl/internal/tui"
|
||||
)
|
||||
|
||||
// version is overridden for releases with -ldflags "-X main.version=<version>".
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
if err := run(os.Args[1:], os.Stdin, os.Stdout); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "proxyctl:", err)
|
||||
@@ -50,11 +53,17 @@ func runWithSystemTargets(args []string, input io.Reader, output io.Writer, syst
|
||||
|
||||
type processEnvironment struct{}
|
||||
|
||||
var (
|
||||
processCurrentUser = profile.SystemCurrentUser
|
||||
processLookupUser = profile.SystemLookupUser
|
||||
processProductionTargets = productionTargets
|
||||
)
|
||||
|
||||
func (processEnvironment) CurrentUser() (profile.UserIdentity, error) {
|
||||
return profile.SystemCurrentUser()
|
||||
return processCurrentUser()
|
||||
}
|
||||
func (processEnvironment) LookupUser(name string) (profile.UserIdentity, error) {
|
||||
return profile.SystemLookupUser(name)
|
||||
return processLookupUser(name)
|
||||
}
|
||||
func (processEnvironment) LookupEnv(name string) (string, bool) { return os.LookupEnv(name) }
|
||||
|
||||
@@ -63,6 +72,15 @@ func runWithEnvironment(args []string, input io.Reader, output io.Writer, enviro
|
||||
}
|
||||
|
||||
func runWithEnvironmentAndTargets(args []string, input io.Reader, output io.Writer, environment profile.IdentityEnvironment, files shell.FileSystem, executable string, systemTargets []target.Target) error {
|
||||
if len(args) == 1 && args[0] == "version" {
|
||||
fmt.Fprintf(output, "proxyctl version %s\n", version)
|
||||
return nil
|
||||
}
|
||||
if len(args) == 2 && args[0] == "version" && args[1] == "--short" {
|
||||
fmt.Fprintln(output, version)
|
||||
return nil
|
||||
}
|
||||
|
||||
profileConfiguration, err := profile.ResolveConfiguration(environment)
|
||||
if err != nil {
|
||||
if len(args) == 1 && args[0] == "doctor" {
|
||||
@@ -79,7 +97,7 @@ func runWithEnvironmentAndTargets(args []string, input io.Reader, output io.Writ
|
||||
}
|
||||
shellTarget := shell.New(shellConfiguration, files, environment)
|
||||
if systemTargets == nil {
|
||||
systemTargets, err = productionTargets(environment)
|
||||
systemTargets, err = processProductionTargets(environment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -129,7 +147,7 @@ func runWithEnvironmentAndTargets(args []string, input io.Reader, output io.Writ
|
||||
case len(args) == 1 && args[0] == "reset":
|
||||
return runMaintenanceCommand(args, input, output, store, targets, mutationLock, logger, nil)
|
||||
default:
|
||||
return fmt.Errorf("usage: proxyctl [doctor|reset|shell install|shell uninstall]")
|
||||
return fmt.Errorf("usage: proxyctl [version [--short]|doctor|reset|shell install|shell uninstall]")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,35 @@ import (
|
||||
"proxyctl/internal/target"
|
||||
)
|
||||
|
||||
func TestVersionOutputIsHumanAndScriptFriendly(t *testing.T) {
|
||||
previous := version
|
||||
version = "v1.2.3"
|
||||
t.Cleanup(func() { version = previous })
|
||||
|
||||
var output bytes.Buffer
|
||||
if err := run([]string{"version"}, strings.NewReader(""), &output); err != nil {
|
||||
t.Fatalf("run version: %v", err)
|
||||
}
|
||||
if got, want := output.String(), "proxyctl version v1.2.3\n"; got != want {
|
||||
t.Fatalf("human version output = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
output.Reset()
|
||||
if err := run([]string{"version", "--short"}, strings.NewReader(""), &output); err != nil {
|
||||
t.Fatalf("run short version: %v", err)
|
||||
}
|
||||
if got, want := output.String(), "v1.2.3\n"; got != want {
|
||||
t.Fatalf("script version output = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionDoesNotRequireUserOrTargetResolution(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
if err := runWithEnvironmentAndTargets([]string{"version", "--short"}, strings.NewReader(""), &output, failingIdentityEnvironment{}, nil, "", nil); err != nil {
|
||||
t.Fatalf("version depended on runtime environment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProductionCompositionRegistersAPTContainerGNOMEAndDaemonTargets(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
environment := commandEnvironment{identity: profile.UserIdentity{Username: "tester", HomeDir: home, UID: os.Getuid(), GID: os.Getgid()}, env: map[string]string{"SHELL": "/bin/bash"}}
|
||||
|
||||
@@ -116,39 +116,75 @@ func TestDoctorStoragePermissionCheckIsReadOnly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaintenanceCommandsRunThroughBuiltBinaryWithIsolatedXDG(t *testing.T) {
|
||||
func TestCommandsRunAsRealProcessesInsideIsolatedUserState(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
binary := filepath.Join(root, "proxyctl")
|
||||
build := exec.Command("go", "build", "-o", binary, ".")
|
||||
binary := filepath.Join(root, "bin", "proxyctl")
|
||||
if err := os.MkdirAll(filepath.Dir(binary), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(root, "home"), 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
build := exec.Command("go", "build", "-tags", "proxyctl_process_test", "-o", binary, ".")
|
||||
if output, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("build proxyctl: %v\n%s", err, output)
|
||||
}
|
||||
environment := isolatedProcessEnvironment(root)
|
||||
|
||||
doctor := exec.Command(binary, "doctor")
|
||||
doctor.Env = environment
|
||||
doctorOutput, doctorErr := doctor.CombinedOutput()
|
||||
if doctorErr == nil || !strings.Contains(string(doctorOutput), "[FAIL]") || !strings.Contains(string(doctorOutput), "Target availability") {
|
||||
invoke := func(input string, args ...string) ([]byte, error) {
|
||||
t.Helper()
|
||||
command := exec.Command(binary, args...)
|
||||
command.Env = environment
|
||||
command.Stdin = strings.NewReader(input)
|
||||
return command.CombinedOutput()
|
||||
}
|
||||
|
||||
defaultOutput, err := invoke("q")
|
||||
if err != nil || !strings.Contains(string(defaultOutput), "proxyctl — Target panel") {
|
||||
t.Fatalf("default process err=%v output=%q", err, defaultOutput)
|
||||
}
|
||||
versionOutput, err := invoke("", "version", "--short")
|
||||
if err != nil || strings.TrimSpace(string(versionOutput)) == "" || strings.Contains(string(versionOutput), " ") {
|
||||
t.Fatalf("version process err=%v output=%q", err, versionOutput)
|
||||
}
|
||||
doctorOutput, doctorErr := invoke("", "doctor")
|
||||
if !strings.Contains(string(doctorOutput), "Profile validity") || (doctorErr != nil && !strings.Contains(string(doctorOutput), "[FAIL]")) {
|
||||
t.Fatalf("doctor process err=%v output=%q", doctorErr, doctorOutput)
|
||||
}
|
||||
|
||||
cancel := exec.Command(binary, "reset")
|
||||
cancel.Env = environment
|
||||
cancel.Stdin = strings.NewReader("no\n")
|
||||
cancelOutput, err := cancel.CombinedOutput()
|
||||
if err != nil || !strings.Contains(string(cancelOutput), "Reset cancelled") {
|
||||
t.Fatalf("cancel process err=%v output=%q", err, cancelOutput)
|
||||
resetOutput, err := invoke("no\n", "reset")
|
||||
if err != nil || !strings.Contains(string(resetOutput), "Reset cancelled") {
|
||||
t.Fatalf("reset cancellation process err=%v output=%q", err, resetOutput)
|
||||
}
|
||||
resetOutput, err = invoke("yes\n", "reset")
|
||||
if err != nil || !strings.Contains(string(resetOutput), "shell: skipped (Disabled)") {
|
||||
t.Fatalf("confirmed reset process err=%v output=%q", err, resetOutput)
|
||||
}
|
||||
installOutput, err := invoke("", "shell", "install")
|
||||
if err != nil || !strings.Contains(string(installOutput), "installed") {
|
||||
t.Fatalf("shell install process err=%v output=%q", err, installOutput)
|
||||
}
|
||||
startup := filepath.Join(root, "home", ".bashrc")
|
||||
startupData, err := os.ReadFile(startup)
|
||||
if err != nil || !strings.Contains(string(startupData), "proxyctl shell integration") {
|
||||
t.Fatalf("isolated startup after install err=%v data=%q", err, startupData)
|
||||
}
|
||||
uninstallOutput, err := invoke("", "shell", "uninstall")
|
||||
if err != nil || !strings.Contains(string(uninstallOutput), "uninstalled") {
|
||||
t.Fatalf("shell uninstall process err=%v output=%q", err, uninstallOutput)
|
||||
}
|
||||
startupData, err = os.ReadFile(startup)
|
||||
if err != nil || strings.Contains(string(startupData), "proxyctl shell integration") {
|
||||
t.Fatalf("isolated startup after uninstall err=%v data=%q", err, startupData)
|
||||
}
|
||||
|
||||
confirmed := exec.Command(binary, "reset")
|
||||
confirmed.Env = environment
|
||||
confirmed.Stdin = strings.NewReader("yes\n")
|
||||
confirmedOutput, err := confirmed.CombinedOutput()
|
||||
if err != nil || !strings.Contains(string(confirmedOutput), "shell: skipped (Disabled)") {
|
||||
t.Fatalf("confirmed process err=%v output=%q", err, confirmedOutput)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(root, "home", ".bashrc")); !os.IsNotExist(err) {
|
||||
t.Fatalf("process smoke test touched a Shell startup file: %v", err)
|
||||
for _, path := range []string{
|
||||
filepath.Join(root, "etc"),
|
||||
filepath.Join(root, "var"),
|
||||
filepath.Join(root, "home", ".docker"),
|
||||
} {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
t.Fatalf("process smoke created prohibited system/Docker fixture %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +201,8 @@ func isolatedProcessEnvironment(root string) []string {
|
||||
"HOME="+filepath.Join(root, "home"),
|
||||
"XDG_CONFIG_HOME="+filepath.Join(root, "config"),
|
||||
"XDG_STATE_HOME="+filepath.Join(root, "state"),
|
||||
"SHELL=/bin/fish",
|
||||
"SHELL=/bin/bash",
|
||||
"PROXYCTL_PROCESS_TEST_HOME="+filepath.Join(root, "home"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//go:build proxyctl_process_test
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
|
||||
"proxyctl/internal/profile"
|
||||
"proxyctl/internal/target"
|
||||
)
|
||||
|
||||
// This file is compiled only into the process-smoke binary. It gives that real
|
||||
// executable an identity whose home is inside the test sandbox and omits
|
||||
// production system Targets, so no command can reach developer or host state.
|
||||
func init() {
|
||||
processProductionTargets = func(profile.IdentityEnvironment) ([]target.Target, error) {
|
||||
return []target.Target{}, nil
|
||||
}
|
||||
processCurrentUser = func() (profile.UserIdentity, error) {
|
||||
home := os.Getenv("PROXYCTL_PROCESS_TEST_HOME")
|
||||
if home == "" {
|
||||
return profile.UserIdentity{}, errors.New("PROXYCTL_PROCESS_TEST_HOME is required by proxyctl_process_test builds")
|
||||
}
|
||||
current, err := user.Current()
|
||||
if err != nil {
|
||||
return profile.UserIdentity{}, err
|
||||
}
|
||||
uid, err := strconv.Atoi(current.Uid)
|
||||
if err != nil {
|
||||
return profile.UserIdentity{}, err
|
||||
}
|
||||
gid, err := strconv.Atoi(current.Gid)
|
||||
if err != nil {
|
||||
return profile.UserIdentity{}, err
|
||||
}
|
||||
return profile.UserIdentity{Username: current.Username, HomeDir: home, UID: uid, GID: gid}, nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user