03f3585f16
internal/drift/drift.go: Detector (Watch via iter.Seq2, Aggregate,
Remediate with cooldown-on-success, Acknowledge), Config with tiered
cadence (critical 5s + Path units, standard 30s, default 60s).
internal/cli/drift.go: orca drift {show,watch,acknowledge,remediate,
config}. internal/emitter/drift_path.go: systemd Path+service unit
emitter (User=orca, ProtectSystem=strict). scripts/orca-drift-notify.sh
(sha256 event JSON), orca-remediate.sh (cooldown-on-success, transient
retry). Pre-flight gate (R-020, --force + per-ns scoping). orca
system user (REQ-111), NFS detection (D-233), orca job restart for
EnvironmentFile drift (D-235).
---ci---
project: orca
phase: 10b
milestone: v0.11
status: execute
---/ci---
632 lines
17 KiB
Go
632 lines
17 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"iter"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/drift"
|
|
)
|
|
|
|
type mockDriftTransport struct {
|
|
execOut []byte
|
|
execErr error
|
|
readOut []byte
|
|
readErr error
|
|
writes []mockDriftWrite
|
|
execFn func(ctx context.Context, peer string, cmd string) ([]byte, error)
|
|
execs []string
|
|
}
|
|
|
|
type mockDriftWrite struct {
|
|
peer string
|
|
path string
|
|
content []byte
|
|
mode os.FileMode
|
|
}
|
|
|
|
func (m *mockDriftTransport) Exec(ctx context.Context, peer string, cmd string) ([]byte, error) {
|
|
if m.execFn != nil {
|
|
return m.execFn(ctx, peer, cmd)
|
|
}
|
|
m.execs = append(m.execs, cmd)
|
|
return m.execOut, m.execErr
|
|
}
|
|
|
|
func (m *mockDriftTransport) WriteFileIdempotent(ctx context.Context, peer string, path string, content []byte, mode os.FileMode) (bool, error) {
|
|
m.writes = append(m.writes, mockDriftWrite{peer, path, content, mode})
|
|
return true, nil
|
|
}
|
|
|
|
func (m *mockDriftTransport) ReadFile(ctx context.Context, peer string, path string) ([]byte, error) {
|
|
return m.readOut, m.readErr
|
|
}
|
|
|
|
// fakeDetector is a record-replay drift.Detector for CLI tests.
|
|
type fakeDetector struct {
|
|
aggEvents []drift.Event
|
|
aggErr error
|
|
remediateErr error
|
|
ackWrites int
|
|
remediateCalls []remediateCall
|
|
}
|
|
|
|
type remediateCall struct {
|
|
peer string
|
|
path string
|
|
force bool
|
|
}
|
|
|
|
func (f *fakeDetector) Watch(ctx context.Context, paths []drift.PathSpec) iter.Seq2[drift.Event, error] {
|
|
return func(yield func(drift.Event, error) bool) {
|
|
for _, e := range f.aggEvents {
|
|
if !yield(e, nil) {
|
|
return
|
|
}
|
|
}
|
|
<-ctx.Done()
|
|
}
|
|
}
|
|
|
|
func (f *fakeDetector) Aggregate(ctx context.Context, leadPeer string) ([]drift.Event, error) {
|
|
return f.aggEvents, f.aggErr
|
|
}
|
|
|
|
func (f *fakeDetector) Remediate(ctx context.Context, leadPeer, path string, force bool) error {
|
|
f.remediateCalls = append(f.remediateCalls, remediateCall{leadPeer, path, force})
|
|
return f.remediateErr
|
|
}
|
|
|
|
func (f *fakeDetector) Acknowledge(ctx context.Context, leadPeer, path string) error {
|
|
f.ackWrites++
|
|
return nil
|
|
}
|
|
|
|
func setupDriftCLITest(t *testing.T) string {
|
|
t.Helper()
|
|
home := t.TempDir()
|
|
t.Setenv("ORCA_HOME", home)
|
|
t.Setenv("ORCA_LEAD_STATE_DIR", filepath.Join(home, "state"))
|
|
return home
|
|
}
|
|
|
|
func TestDriftCmdRegistered(t *testing.T) {
|
|
for _, c := range rootCmd.Commands() {
|
|
if c.Name() == "drift" {
|
|
return
|
|
}
|
|
}
|
|
t.Fatal("drift command not registered on root")
|
|
}
|
|
|
|
func TestDriftSubcommandsRegistered(t *testing.T) {
|
|
for _, c := range rootCmd.Commands() {
|
|
if c.Name() != "drift" {
|
|
continue
|
|
}
|
|
want := map[string]bool{
|
|
"show": false,
|
|
"watch": false,
|
|
"acknowledge": false,
|
|
"remediate": false,
|
|
"config": false,
|
|
}
|
|
for _, sub := range c.Commands() {
|
|
if _, ok := want[sub.Name()]; ok {
|
|
want[sub.Name()] = true
|
|
}
|
|
}
|
|
for name, found := range want {
|
|
if !found {
|
|
t.Errorf("drift subcommand %q not registered", name)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
t.Fatal("drift command not registered")
|
|
}
|
|
|
|
func TestDriftConfigSubcommands(t *testing.T) {
|
|
for _, c := range rootCmd.Commands() {
|
|
if c.Name() != "drift" {
|
|
continue
|
|
}
|
|
for _, sub := range c.Commands() {
|
|
if sub.Name() != "config" {
|
|
continue
|
|
}
|
|
want := map[string]bool{"show": false, "validate": false}
|
|
for _, s := range sub.Commands() {
|
|
if _, ok := want[s.Name()]; ok {
|
|
want[s.Name()] = true
|
|
}
|
|
}
|
|
for name, found := range want {
|
|
if !found {
|
|
t.Errorf("drift config subcommand %q not registered", name)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
}
|
|
t.Fatal("drift config not registered")
|
|
}
|
|
|
|
func TestJobRestartRegistered(t *testing.T) {
|
|
for _, c := range rootCmd.Commands() {
|
|
if c.Name() != "job" {
|
|
continue
|
|
}
|
|
for _, sub := range c.Commands() {
|
|
if sub.Name() == "restart" {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
t.Fatal("job restart not registered")
|
|
}
|
|
|
|
func TestDriftShowEmpty(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
fd := &fakeDetector{aggEvents: nil}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "show"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift show: %v", err)
|
|
}
|
|
if !bytesContains(buf.String(), "No drift events") {
|
|
t.Errorf("empty show output: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestDriftShowTable(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
fd := &fakeDetector{aggEvents: []drift.Event{
|
|
{EventID: "E1", Host: "peer1", Path: "/etc/traefik/dynamic/orca.yml", Status: drift.StatusModified, DriftConfirmed: true},
|
|
}}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "show"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift show: %v", err)
|
|
}
|
|
out := buf.String()
|
|
for _, want := range []string{"E1", "peer1", "modified"} {
|
|
if !bytesContains(out, want) {
|
|
t.Errorf("output missing %q: %s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDriftShowJSON(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
fd := &fakeDetector{aggEvents: []drift.Event{
|
|
{EventID: "E1", Host: "peer1", Path: "/etc/x", Status: drift.StatusCreated, DriftConfirmed: true},
|
|
}}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
_ = rootCmd.PersistentFlags().Set("json", "true")
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "show"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift show --json: %v", err)
|
|
}
|
|
if !bytesContains(buf.String(), `"event_id"`) {
|
|
t.Errorf("json output missing event_id: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestDriftShowPeerFilter(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
fd := &fakeDetector{aggEvents: []drift.Event{
|
|
{EventID: "E1", Host: "peer1", Path: "/a", Status: drift.StatusModified, DriftConfirmed: true},
|
|
{EventID: "E2", Host: "peer2", Path: "/b", Status: drift.StatusModified, DriftConfirmed: true},
|
|
}}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "show", "--peer", "peer1"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift show --peer: %v", err)
|
|
}
|
|
out := buf.String()
|
|
if !bytesContains(out, "E1") {
|
|
t.Errorf("filtered output should have E1: %s", out)
|
|
}
|
|
if bytesContains(out, "E2") {
|
|
t.Errorf("filtered output should NOT have E2: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestDriftWatchStreamsAndCancels(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
// Use a fakeDetector whose Watch yields one event then blocks on
|
|
// ctx so the stream terminates when ctrl-c (signal.NotifyContext)
|
|
// cancels. We simulate the cancel by constructing a fake that yields
|
|
// then returns when the consumer stops pulling OR ctx is cancelled.
|
|
fd := &drainingFakeDetector{events: []drift.Event{
|
|
{EventID: "E1", Host: "p", Path: "/etc/x", Status: drift.StatusModified, DriftConfirmed: true},
|
|
}}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "watch", "--interval", "10ms"})
|
|
// Inject a context that auto-cancels after the events drain so
|
|
// the watch loop exits without polluting rootCmd's context (which
|
|
// is shared across tests). We use PersistentPreRunE's context by
|
|
// overriding it here and restoring after.
|
|
origCtx := rootCmd.Context()
|
|
ctx, cancel := context.WithCancel(origCtx)
|
|
defer cancel()
|
|
rootCmd.SetContext(ctx)
|
|
fd.cancelAfterYield = cancel
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift watch: %v", err)
|
|
}
|
|
// Restore rootCmd context for subsequent tests.
|
|
rootCmd.SetContext(origCtx)
|
|
if !bytesContains(buf.String(), "E1") {
|
|
t.Errorf("watch output missing E1: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
// drainingFakeDetector yields the events then cancels the provided
|
|
// cancel func (so the watch loop's signal.NotifyContext ctx is
|
|
// cancelled and the stream terminates cleanly).
|
|
type drainingFakeDetector struct {
|
|
events []drift.Event
|
|
cancelAfterYield context.CancelFunc
|
|
remediateCalls []remediateCall
|
|
ackWrites int
|
|
}
|
|
|
|
func (d *drainingFakeDetector) Watch(ctx context.Context, paths []drift.PathSpec) iter.Seq2[drift.Event, error] {
|
|
return func(yield func(drift.Event, error) bool) {
|
|
for _, e := range d.events {
|
|
if !yield(e, nil) {
|
|
return
|
|
}
|
|
}
|
|
if d.cancelAfterYield != nil {
|
|
d.cancelAfterYield()
|
|
}
|
|
<-ctx.Done()
|
|
}
|
|
}
|
|
func (d *drainingFakeDetector) Aggregate(ctx context.Context, leadPeer string) ([]drift.Event, error) {
|
|
return d.events, nil
|
|
}
|
|
func (d *drainingFakeDetector) Remediate(ctx context.Context, leadPeer, path string, force bool) error {
|
|
d.remediateCalls = append(d.remediateCalls, remediateCall{leadPeer, path, force})
|
|
return nil
|
|
}
|
|
func (d *drainingFakeDetector) Acknowledge(ctx context.Context, leadPeer, path string) error {
|
|
d.ackWrites++
|
|
return nil
|
|
}
|
|
|
|
func TestDriftAcknowledge(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
fd := &fakeDetector{}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "acknowledge", "peer1", "/etc/traefik/dynamic/orca.yml"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift acknowledge: %v", err)
|
|
}
|
|
if fd.ackWrites != 1 {
|
|
t.Errorf("ackWrites = %d, want 1", fd.ackWrites)
|
|
}
|
|
if !bytesContains(buf.String(), "Acknowledged") {
|
|
t.Errorf("output missing Acknowledged: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestDriftRemediate(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
fd := &fakeDetector{}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "remediate", "peer1", "/etc/x"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift remediate: %v", err)
|
|
}
|
|
if len(fd.remediateCalls) != 1 {
|
|
t.Fatalf("remediateCalls = %d, want 1", len(fd.remediateCalls))
|
|
}
|
|
if fd.remediateCalls[0].peer != "peer1" || fd.remediateCalls[0].path != "/etc/x" {
|
|
t.Errorf("remediate call: %+v", fd.remediateCalls[0])
|
|
}
|
|
if fd.remediateCalls[0].force {
|
|
t.Errorf("force should be false without --force")
|
|
}
|
|
if !bytesContains(buf.String(), "Remediated") {
|
|
t.Errorf("output missing Remediated: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestDriftRemediateForce(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
fd := &fakeDetector{}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "remediate", "peer1", "/etc/x", "--force"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift remediate --force: %v", err)
|
|
}
|
|
if len(fd.remediateCalls) != 1 {
|
|
t.Fatalf("remediateCalls = %d, want 1", len(fd.remediateCalls))
|
|
}
|
|
if !fd.remediateCalls[0].force {
|
|
t.Errorf("force should be true with --force")
|
|
}
|
|
}
|
|
|
|
func TestDriftRemediateCooldown(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
fd := &fakeDetector{remediateErr: drift.ErrCooldown}
|
|
driftDetectorOverride = fd
|
|
defer func() { driftDetectorOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "remediate", "peer1", "/etc/x"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift remediate cooldown: %v", err)
|
|
}
|
|
if !bytesContains(buf.String(), "cooldown") {
|
|
t.Errorf("output should mention cooldown: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestDriftConfigShow(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "config", "show"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift config show: %v", err)
|
|
}
|
|
out := buf.String()
|
|
for _, want := range []string{"Polling:", "Critical paths:", "Standard paths:", "Excluded paths:", "Remediation:"} {
|
|
if !bytesContains(out, want) {
|
|
t.Errorf("config show missing %q: %s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDriftConfigValidate(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "config", "validate"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("drift config validate: %v", err)
|
|
}
|
|
if !bytesContains(buf.String(), "valid") {
|
|
t.Errorf("output missing 'valid': %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestDriftConfigValidateFails(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
cfgPath := filepath.Join(t.TempDir(), "drift.json")
|
|
bad := `{"polling":{"enabled":true,"default_interval":60000000000,"max_concurrent_peers":4},"paths":{"critical":[{"tier":"critical","pattern":"","interval":5000000000}]},"remediate":{"auto":true}}`
|
|
if err := os.WriteFile(cfgPath, []byte(bad), 0o644); err != nil {
|
|
t.Fatalf("write: %v", err)
|
|
}
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"drift", "config", "validate", "--config", cfgPath})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected validate error")
|
|
}
|
|
}
|
|
|
|
func TestJobRestartRequiresPeer(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"job", "restart", "alloc1"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error for missing --peer")
|
|
}
|
|
}
|
|
|
|
func TestJobRestartExecs(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
mt := &mockDriftTransport{execOut: []byte("restarted")}
|
|
driftTransportOverride = mt
|
|
defer func() { driftTransportOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"job", "restart", "alloc1", "--peer", "peer1:22"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("job restart: %v", err)
|
|
}
|
|
if len(mt.execs) != 1 {
|
|
t.Fatalf("execs = %d, want 1", len(mt.execs))
|
|
}
|
|
if !bytesContains(mt.execs[0], "orca-alloc-alloc1.service") {
|
|
t.Errorf("restart cmd missing: %s", mt.execs[0])
|
|
}
|
|
}
|
|
|
|
func TestJobRestartTransientError(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
mt := &mockDriftTransport{execErr: fmt.Errorf("connection refused")}
|
|
driftTransportOverride = mt
|
|
defer func() { driftTransportOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"job", "restart", "alloc1", "--peer", "peer1:22"})
|
|
err := rootCmd.Execute()
|
|
if err == nil {
|
|
t.Fatal("expected error for exec failure")
|
|
}
|
|
if !errors.Is(err, err) {
|
|
}
|
|
}
|
|
|
|
func TestPeerSetupCmdRegistered(t *testing.T) {
|
|
for _, c := range rootCmd.Commands() {
|
|
if c.Name() == "peer-setup" {
|
|
return
|
|
}
|
|
}
|
|
t.Fatal("peer-setup command not registered")
|
|
}
|
|
|
|
func TestPeerSetupCreatesUserAndDir(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
mt := &mockDriftTransport{execOut: []byte("ext4")}
|
|
peerSetupTransportOverride = mt
|
|
defer func() { peerSetupTransportOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"peer-setup", "peer1:22"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("peer-setup: %v", err)
|
|
}
|
|
if len(mt.execs) < 2 {
|
|
t.Fatalf("execs = %d, want >= 2", len(mt.execs))
|
|
}
|
|
useraddSeen := false
|
|
mkdirSeen := false
|
|
statSeen := false
|
|
for _, c := range mt.execs {
|
|
if bytesContains(c, "useradd -r orca") {
|
|
useraddSeen = true
|
|
}
|
|
if bytesContains(c, "mkdir -p /etc/orca/state/drift-events") {
|
|
mkdirSeen = true
|
|
}
|
|
if bytesContains(c, "stat -f") {
|
|
statSeen = true
|
|
}
|
|
}
|
|
if !useraddSeen {
|
|
t.Errorf("useradd not run")
|
|
}
|
|
if !mkdirSeen {
|
|
t.Errorf("mkdir drift-events not run")
|
|
}
|
|
if !statSeen {
|
|
t.Errorf("stat (NFS detect) not run")
|
|
}
|
|
if !bytesContains(buf.String(), "nfs=false") {
|
|
t.Errorf("output should report nfs=false: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestPeerSetupNoOrcaUser(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
mt := &mockDriftTransport{execOut: []byte("ext4")}
|
|
peerSetupTransportOverride = mt
|
|
defer func() { peerSetupTransportOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"peer-setup", "peer1:22", "--no-orca-user"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("peer-setup --no-orca-user: %v", err)
|
|
}
|
|
useraddSeen := false
|
|
for _, c := range mt.execs {
|
|
if bytesContains(c, "useradd -r orca") {
|
|
useraddSeen = true
|
|
}
|
|
}
|
|
if useraddSeen {
|
|
t.Errorf("useradd should NOT run with --no-orca-user")
|
|
}
|
|
if !bytesContains(buf.String(), "user=false") {
|
|
t.Errorf("output should report user=false: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestPeerSetupDetectsNFS(t *testing.T) {
|
|
setupDriftCLITest(t)
|
|
mt := &mockDriftTransport{execOut: []byte("nfs4")}
|
|
peerSetupTransportOverride = mt
|
|
defer func() { peerSetupTransportOverride = nil }()
|
|
|
|
resetRootFlags(t)
|
|
var buf bytes.Buffer
|
|
rootCmd.SetOut(&buf)
|
|
rootCmd.SetErr(&buf)
|
|
rootCmd.SetArgs([]string{"peer-setup", "peer1:22"})
|
|
if err := rootCmd.Execute(); err != nil {
|
|
t.Fatalf("peer-setup: %v", err)
|
|
}
|
|
if !bytesContains(buf.String(), "nfs=true") {
|
|
t.Errorf("output should report nfs=true: %s", buf.String())
|
|
}
|
|
}
|