Files
proxy/internal/dockerdaemon/dockerdaemon_test.go
T

615 lines
24 KiB
Go

package dockerdaemon_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"proxyctl/internal/app"
"proxyctl/internal/dockerdaemon"
"proxyctl/internal/profile"
"proxyctl/internal/target"
"proxyctl/internal/target/targettest"
)
type recorder struct{ events []string }
type fakeRunner struct {
recorder *recorder
show string
fail map[string]error
}
func (r *fakeRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {
command := strings.Join(append([]string{name}, args...), " ")
r.recorder.events = append(r.recorder.events, command)
if err := r.fail[command]; err != nil {
return nil, err
}
if strings.Contains(command, " show ") {
return []byte(r.show), nil
}
if strings.Contains(command, " is-active ") {
return []byte("active\n"), nil
}
return nil, nil
}
type recordingFS struct{ recorder *recorder }
func (f recordingFS) ReadFile(path string) ([]byte, error) { return os.ReadFile(path) }
func (f recordingFS) FileMode(path string) (os.FileMode, error) {
info, err := os.Stat(path)
if err != nil {
return 0, err
}
return info.Mode().Perm(), nil
}
func (f recordingFS) AtomicWrite(path string, data []byte, mode os.FileMode) error {
f.recorder.events = append(f.recorder.events, "write "+filepath.Base(path))
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return err
}
temporary := path + ".tmp"
if err := os.WriteFile(temporary, data, mode); err != nil {
return err
}
return os.Rename(temporary, path)
}
func (f recordingFS) Remove(path string) error {
f.recorder.events = append(f.recorder.events, "remove "+filepath.Base(path))
return os.Remove(path)
}
func fixture(t *testing.T) (*dockerdaemon.Adapter, *recorder, string, string, *fakeRunner) {
t.Helper()
directory := t.TempDir()
daemonPath := filepath.Join(directory, "etc", "docker", "daemon.json")
recoveryPath := filepath.Join(directory, "state", "docker-daemon.json")
record := &recorder{}
runner := &fakeRunner{recorder: record, show: "LoadState=loaded\nFragmentPath=/lib/systemd/system/docker.service\n", fail: map[string]error{}}
adapter := dockerdaemon.New(dockerdaemon.Configuration{DaemonConfigPath: daemonPath, RecoveryPath: recoveryPath, TrustedRoot: directory}, recordingFS{recorder: record}, runner)
return adapter, record, daemonPath, recoveryPath, runner
}
func activeProfile() *profile.Profile {
return &profile.Profile{Name: "office", Mode: profile.AdvancedMode, HTTPURL: "http://alice:secret@proxy.example:8080", HTTPSURL: "https://proxy.example:8443", Bypass: []string{"localhost", ".example.com", "10.0.0.0/8", "2001:db8::1"}}
}
type faultFS struct {
recordingFS
permission bool
failDaemon bool
failRecoveryOnce bool
}
func (f *faultFS) AtomicWrite(path string, data []byte, mode os.FileMode) error {
if f.permission {
return os.ErrPermission
}
if f.failDaemon && filepath.Base(path) == "daemon.json" {
return errors.New("write failed")
}
if f.failRecoveryOnce && filepath.Base(path) == "docker-daemon.json" {
f.failRecoveryOnce = false
return errors.New("secret recovery failure")
}
return f.recordingFS.AtomicWrite(path, data, mode)
}
type fixedAuthority int
func (a fixedAuthority) EffectiveUID() int { return int(a) }
type immediateLock struct{}
func (immediateLock) Lock(context.Context) (func(), error) { return func() {}, nil }
func TestUnsafeRecoveryMetadataIsDriftedAndResetNeverMutatesDaemon(t *testing.T) {
fingerprint := strings.Repeat("a", 64)
cases := map[string]string{
"empty object": `{}`,
"truncated": `{"version":1`,
"unsupported version": `{"version":2,"originalExists":false,"configFingerprint":"` + fingerprint + `"}`,
"missing original": `{"version":1,"originalExists":true,"originalMode":384,"configFingerprint":"` + fingerprint + `"}`,
"unexpected original": `{"version":1,"originalExists":false,"original":"e30=","configFingerprint":"` + fingerprint + `"}`,
"mode without original": `{"version":1,"originalExists":false,"originalMode":384,"configFingerprint":"` + fingerprint + `"}`,
"invalid mode": `{"version":1,"originalExists":true,"original":"e30=","originalMode":4096,"configFingerprint":"` + fingerprint + `"}`,
"invalid fingerprint": `{"version":1,"originalExists":false,"configFingerprint":"short"}`,
"contradictory restart record": `{"version":1,"originalExists":false,"configFingerprint":"` + fingerprint + `","restartedFingerprint":"` + strings.Repeat("b", 64) + `"}`,
}
for name, metadata := range cases {
t.Run(name, func(t *testing.T) {
a, record, daemonPath, recoveryPath, _ := fixture(t)
originalConfig := []byte(`{"log-level":"debug"}`)
if err := os.MkdirAll(filepath.Dir(daemonPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(daemonPath, originalConfig, 0o640); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Dir(recoveryPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(recoveryPath, []byte(metadata), 0o600); err != nil {
t.Fatal(err)
}
record.events = nil
if inspection := a.Inspect(context.Background(), nil); inspection.State != target.Drifted {
t.Fatalf("inspection = %#v", inspection)
}
reset := app.NewMaintenance(profile.State{}, []target.Target{a}, immediateLock{}, nil).Reset(context.Background(), true)
if reset.Err == nil || len(reset.Targets) != 1 || reset.Targets[0].Err == nil {
t.Fatalf("unsafe reset = %#v", reset)
}
if events := mutationEvents(record.events); len(events) != 0 {
t.Fatalf("unsafe metadata caused mutation: %#v", events)
}
if got, err := os.ReadFile(daemonPath); err != nil || !reflect.DeepEqual(got, originalConfig) {
t.Fatalf("daemon changed to %q: %v", got, err)
}
if got, err := os.ReadFile(recoveryPath); err != nil || string(got) != metadata {
t.Fatalf("recovery changed to %q: %v", got, err)
}
})
}
}
func TestDiagnosticChecksCoverSystemdSchemaAndAuthorityWithoutMutation(t *testing.T) {
a, record, daemonPath, recoveryPath, runner := fixture(t)
a = dockerdaemon.New(dockerdaemon.Configuration{DaemonConfigPath: daemonPath, RecoveryPath: recoveryPath, TrustedRoot: filepath.Dir(filepath.Dir(daemonPath))}, recordingFS{recorder: record}, runner, fixedAuthority(0))
checks := a.DiagnosticChecks(context.Background(), activeProfile())
for _, name := range []string{"required command and service", "configuration schema", "write authority", "service control authority"} {
found := false
for _, check := range checks {
found = found || check.Name == name
}
if !found {
t.Errorf("diagnostics missing %q: %#v", name, checks)
}
}
for _, event := range record.events {
if strings.HasPrefix(event, "write ") || strings.HasPrefix(event, "remove ") || strings.Contains(event, " restart ") || strings.Contains(event, "daemon-reload") {
t.Fatalf("diagnostics mutated daemon state: %#v", record.events)
}
}
for _, path := range []string{daemonPath, recoveryPath} {
if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("diagnostics created %s: %v", path, err)
}
}
}
func TestDiagnosticServiceControlAuthorityUsesInjectedEUIDWithoutMutation(t *testing.T) {
for _, test := range []struct {
name string
uid int
ok bool
}{
{name: "unprivileged", uid: 1000, ok: false},
{name: "root", uid: 0, ok: true},
} {
t.Run(test.name, func(t *testing.T) {
_, record, daemonPath, recoveryPath, runner := fixture(t)
a := dockerdaemon.New(dockerdaemon.Configuration{DaemonConfigPath: daemonPath, RecoveryPath: recoveryPath}, recordingFS{recorder: record}, runner, fixedAuthority(test.uid))
checks := a.DiagnosticChecks(context.Background(), activeProfile())
var found *target.Check
for i := range checks {
if checks[i].Name == "service control authority" {
found = &checks[i]
}
}
if found == nil || found.OK != test.ok {
t.Fatalf("service authority check = %#v", found)
}
for _, event := range record.events {
if strings.Contains(event, "restart") || strings.Contains(event, "daemon-reload") {
t.Fatalf("authority diagnosis mutated service: %#v", record.events)
}
}
})
}
}
func TestDiagnosticsAndEnableRejectMalformedDaemonProxyTypesWithoutMutation(t *testing.T) {
for _, document := range []string{
`[]`,
`{"proxies":[]}`,
`{"proxies":{"http-proxy":42}}`,
`{"proxies":{"https-proxy":false}}`,
`{"proxies":{"no-proxy":[]}}`,
} {
t.Run(document, func(t *testing.T) {
a, record, daemonPath, _, _ := fixture(t)
if err := os.MkdirAll(filepath.Dir(daemonPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(daemonPath, []byte(document), 0o600); err != nil {
t.Fatal(err)
}
checks := a.DiagnosticChecks(context.Background(), activeProfile())
if checks[1].OK {
t.Fatalf("malformed schema passed diagnostics: %#v", checks)
}
record.events = nil
result := a.SetDesired(context.Background(), true, activeProfile())
if result.Err == nil {
t.Fatalf("enable accepted malformed schema: %#v", result)
}
for _, event := range record.events {
if strings.HasPrefix(event, "write ") || strings.Contains(event, "restart") || strings.Contains(event, "daemon-reload") {
t.Fatalf("malformed schema caused mutation: %#v", record.events)
}
}
got, err := os.ReadFile(daemonPath)
if err != nil || string(got) != document {
t.Fatalf("daemon config changed: %q, %v", got, err)
}
})
}
}
func TestDockerDaemonRunsReusableTargetContract(t *testing.T) {
targettest.Run(t, func(t *testing.T) targettest.Fixture {
a, _, daemonPath, _, _ := fixture(t)
return targettest.Fixture{
Target: a, Active: activeProfile(), Secret: "secret",
MakeExternal: func(t *testing.T) {
if err := os.MkdirAll(filepath.Dir(daemonPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(daemonPath, []byte(`{"proxies":{"http-proxy":"http://external.example:8080"}}`), 0o600); err != nil {
t.Fatal(err)
}
},
MakeDrifted: func(t *testing.T) {
if err := os.WriteFile(daemonPath, []byte(`{"proxies":{"http-proxy":"http://changed.example:8080"}}`), 0o600); err != nil {
t.Fatal(err)
}
},
PermissionFailure: func(t *testing.T) target.OperationResult {
failed, record, _, _, failedRunner := fixture(t)
files := &faultFS{recordingFS: recordingFS{recorder: record}, permission: true}
failed = dockerdaemon.New(dockerdaemon.Configuration{DaemonConfigPath: failed.DaemonConfigPath(), RecoveryPath: failed.RecoveryPath(), TrustedRoot: filepath.Dir(filepath.Dir(failed.DaemonConfigPath()))}, files, failedRunner)
return failed.SetDesired(context.Background(), true, activeProfile())
},
PartialFailure: func(t *testing.T) target.OperationResult {
failed, record, _, _, failedRunner := fixture(t)
files := &faultFS{recordingFS: recordingFS{recorder: record}, failDaemon: true}
failed = dockerdaemon.New(dockerdaemon.Configuration{DaemonConfigPath: failed.DaemonConfigPath(), RecoveryPath: failed.RecoveryPath()}, files, failedRunner)
return failed.SetDesired(context.Background(), true, activeProfile())
},
RedactionFailure: func(t *testing.T) target.OperationResult {
failed, _, _, _, failedRunner := fixture(t)
failedRunner.fail["systemctl daemon-reload"] = errors.New("http://alice:secret@proxy.example:8080")
return failed.SetDesired(context.Background(), true, activeProfile())
},
RecoveryRetry: func(t *testing.T) (target.OperationResult, target.OperationResult, target.OperationResult) {
failed, record, daemon, recovery, failedRunner := fixture(t)
if err := os.MkdirAll(filepath.Dir(daemon), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(daemon, []byte(`{"proxies":{"http-proxy":"http://external.example:8080"}}`), 0o600); err != nil {
t.Fatal(err)
}
files := &faultFS{recordingFS: recordingFS{recorder: record}, failRecoveryOnce: true}
failed = dockerdaemon.New(dockerdaemon.Configuration{DaemonConfigPath: daemon, RecoveryPath: recovery}, files, failedRunner)
first := failed.SetDesired(context.Background(), true, activeProfile())
retry := failed.SetDesired(context.Background(), true, activeProfile())
disabled := failed.SetDesired(context.Background(), false, nil)
return first, retry, disabled
},
}
})
}
func TestDockerDaemonAvailabilityDescriptionAndConfirmation(t *testing.T) {
a, _, _, _, runner := fixture(t)
if got := a.Availability(context.Background()); !got.Available || !strings.Contains(got.Evidence, "systemd") {
t.Fatalf("availability = %#v", got)
}
description := a.Description()
for _, phrase := range []string{"registry", "not container network traffic", "restart", "permission"} {
if !strings.Contains(strings.ToLower(description.Summary+" "+description.Details), phrase) {
t.Errorf("description misses %q: %#v", phrase, description)
}
}
if warning := a.Confirmation(true); !strings.Contains(strings.ToLower(warning), "restart") || !strings.Contains(strings.ToLower(warning), "running") {
t.Fatalf("confirmation = %q", warning)
}
runner.show = "LoadState=not-found\nFragmentPath=\n"
if got := a.Availability(context.Background()); got.Available || !strings.Contains(got.Evidence, "not detected") {
t.Fatalf("unsupported availability = %#v", got)
}
}
func TestDockerDaemonEnableOrdersManagedWriteReloadRestartAndPostInspect(t *testing.T) {
a, record, daemonPath, _, _ := fixture(t)
result := a.SetDesired(context.Background(), true, activeProfile())
if result.Err != nil || result.Inspection.State != target.Enabled {
t.Fatalf("enable = %#v", result)
}
wantOrder := []string{"write docker-daemon.json", "write daemon.json", "systemctl daemon-reload", "systemctl restart docker.service", "systemctl is-active docker.service", "write docker-daemon.json"}
assertSubsequence(t, record.events, wantOrder)
var config map[string]any
data, err := os.ReadFile(daemonPath)
if err != nil || json.Unmarshal(data, &config) != nil {
t.Fatalf("daemon config: %v %s", err, data)
}
proxies := config["proxies"].(map[string]any)
if proxies["http-proxy"] != activeProfile().HTTPURL || proxies["https-proxy"] != activeProfile().HTTPSURL || !strings.Contains(proxies["no-proxy"].(string), "10.0.0.0/8") {
t.Fatalf("managed proxies = %#v", proxies)
}
record.events = nil
second := a.SetDesired(context.Background(), true, activeProfile())
if second.Err != nil || second.Inspection.State != target.Enabled || len(mutationEvents(record.events)) != 0 {
t.Fatalf("idempotent enable = %#v, events=%v", second, record.events)
}
}
func TestDockerDaemonDisableAndResetRejectDriftWithoutMutatingDaemonOrRecovery(t *testing.T) {
for _, test := range []struct {
name string
mutate func(t *testing.T, managed []byte) []byte
}{
{
name: "unrelated administrator edit",
mutate: func(t *testing.T, managed []byte) []byte {
t.Helper()
var document map[string]any
if err := json.Unmarshal(managed, &document); err != nil {
t.Fatal(err)
}
document["log-level"] = "debug"
changed, err := json.MarshalIndent(document, "", " ")
if err != nil {
t.Fatal(err)
}
return append(changed, '\n')
},
},
{
name: "managed proxy edit",
mutate: func(t *testing.T, managed []byte) []byte {
t.Helper()
var document map[string]any
if err := json.Unmarshal(managed, &document); err != nil {
t.Fatal(err)
}
document["proxies"].(map[string]any)["http-proxy"] = "http://administrator.example:3128"
changed, err := json.MarshalIndent(document, "", " ")
if err != nil {
t.Fatal(err)
}
return append(changed, '\n')
},
},
} {
t.Run(test.name, func(t *testing.T) {
a, record, daemonPath, recoveryPath, _ := fixture(t)
if result := a.SetDesired(context.Background(), true, activeProfile()); result.Err != nil {
t.Fatal(result.Err)
}
managed, err := os.ReadFile(daemonPath)
if err != nil {
t.Fatal(err)
}
changed := test.mutate(t, managed)
if err := os.WriteFile(daemonPath, changed, 0o600); err != nil {
t.Fatal(err)
}
recoveryBefore, err := os.ReadFile(recoveryPath)
if err != nil {
t.Fatal(err)
}
record.events = nil
reset := app.NewMaintenance(profile.State{Profiles: []profile.Profile{*activeProfile()}, ActiveProfile: activeProfile().Name}, []target.Target{a}, immediateLock{}, nil).Reset(context.Background(), true)
if reset.Err == nil || len(reset.Targets) != 1 || reset.Targets[0].Err == nil || reset.Targets[0].Inspection.State != target.Drifted {
t.Fatalf("conflicting reset = %#v", reset)
}
if events := mutationEvents(record.events); len(events) != 0 {
t.Fatalf("conflicting reset mutated daemon state: %#v", events)
}
if got, err := os.ReadFile(daemonPath); err != nil || !reflect.DeepEqual(got, changed) {
t.Fatalf("current daemon configuration changed to %q: %v", got, err)
}
if got, err := os.ReadFile(recoveryPath); err != nil || !reflect.DeepEqual(got, recoveryBefore) {
t.Fatalf("recovery metadata changed to %q: %v", got, err)
}
})
}
}
func TestDockerDaemonResetCleansAlreadyRestoredRecoveryWithoutDaemonWriteOrSystemctl(t *testing.T) {
a, record, daemonPath, recoveryPath, _ := fixture(t)
original := []byte("{\n \"log-level\": \"debug\",\n \"proxies\": {\"http-proxy\": \"http://prior.example:3128\"}\n}\n")
if err := os.MkdirAll(filepath.Dir(daemonPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(daemonPath, original, 0o640); err != nil {
t.Fatal(err)
}
if result := a.SetDesired(context.Background(), true, activeProfile()); result.Err != nil {
t.Fatal(result.Err)
}
if err := os.WriteFile(daemonPath, original, 0o640); err != nil {
t.Fatal(err)
}
record.events = nil
reset := app.NewMaintenance(profile.State{Profiles: []profile.Profile{*activeProfile()}, ActiveProfile: activeProfile().Name}, []target.Target{a}, immediateLock{}, nil).Reset(context.Background(), true)
if reset.Err != nil || len(reset.Targets) != 1 || reset.Targets[0].Err != nil || reset.Targets[0].Inspection.State != target.External {
t.Fatalf("already-restored reset = %#v", reset)
}
for _, event := range record.events {
if event == "write daemon.json" || strings.Contains(event, "daemon-reload") || strings.Contains(event, " restart ") {
t.Fatalf("already-restored reset changed daemon or systemd state: %#v", record.events)
}
}
if !contains(record.events, "remove docker-daemon.json") {
t.Fatalf("already-restored reset did not clean recovery metadata: %#v", record.events)
}
if got, err := os.ReadFile(daemonPath); err != nil || !reflect.DeepEqual(got, original) {
t.Fatalf("already-restored daemon configuration changed to %q: %v", got, err)
}
if info, err := os.Stat(daemonPath); err != nil || info.Mode().Perm() != 0o640 {
t.Fatalf("already-restored daemon metadata changed: info=%v err=%v", info, err)
}
if _, err := os.Stat(recoveryPath); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("recovery metadata remains: %v", err)
}
record.events = nil
retry := a.SetDesired(context.Background(), false, nil)
if retry.Err != nil || retry.Inspection.State != target.External || len(mutationEvents(record.events)) != 0 {
t.Fatalf("already-restored cleanup retry = %#v events=%#v", retry, record.events)
}
}
func TestDockerDaemonPreservesAndRestoresExistingConfiguration(t *testing.T) {
a, _, daemonPath, recoveryPath, _ := fixture(t)
original := []byte("{\n \"log-level\": \"debug\",\n \"proxies\": {\"http-proxy\": \"http://prior.example:3128\"}\n}\n")
if err := os.MkdirAll(filepath.Dir(daemonPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(daemonPath, original, 0o640); err != nil {
t.Fatal(err)
}
if got := a.Inspect(context.Background(), activeProfile()).State; got != target.External {
t.Fatalf("initial = %s", got)
}
if got := a.SetDesired(context.Background(), true, activeProfile()); got.Err != nil {
t.Fatal(got.Err)
}
if got := a.SetDesired(context.Background(), false, nil); got.Err != nil || got.Inspection.State != target.External {
t.Fatalf("disable = %#v", got)
}
restored, _ := os.ReadFile(daemonPath)
if !reflect.DeepEqual(restored, original) {
t.Fatalf("restored:\n%s\nwant:\n%s", restored, original)
}
if info, err := os.Stat(daemonPath); err != nil || info.Mode().Perm() != 0o640 {
t.Fatalf("restored mode = %v, %v", info, err)
}
if _, err := os.Stat(recoveryPath); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("recovery remains: %v", err)
}
}
func TestDockerDaemonRestartFailureIsRedactedTruthfulAndRetryable(t *testing.T) {
a, record, _, _, runner := fixture(t)
restart := "systemctl restart docker.service"
runner.fail[restart] = fmt.Errorf("restart exposed http://alice:secret@proxy.example:8080")
first := a.SetDesired(context.Background(), true, activeProfile())
if first.Err == nil || strings.Contains(first.Err.Error(), "secret") || first.Inspection.State != target.Enabled {
t.Fatalf("restart failure = %#v", first)
}
delete(runner.fail, restart)
record.events = nil
retry := a.SetDesired(context.Background(), true, activeProfile())
if retry.Err != nil || retry.Inspection.State != target.Enabled {
t.Fatalf("retry = %#v", retry)
}
if contains(record.events, "write daemon.json") || !contains(record.events, restart) {
t.Fatalf("retry events = %v", record.events)
}
}
func TestDockerDaemonRejectsIncompatibleProfileBeforeMutation(t *testing.T) {
for _, p := range []*profile.Profile{
{Name: "socks", Mode: profile.SingleMode, SingleURL: "socks5://proxy.example:1080"},
{Name: "all", Mode: profile.AdvancedMode, HTTPURL: "http://proxy.example:8080", AllURL: "http://proxy.example:8080"},
{Name: "bypass", Mode: profile.AdvancedMode, HTTPURL: "http://proxy.example:8080", Bypass: []string{"*"}},
} {
t.Run(p.Name, func(t *testing.T) {
a, record, _, _, _ := fixture(t)
result := a.SetDesired(context.Background(), true, p)
if result.Err == nil || len(mutationEvents(record.events)) != 0 || result.Inspection.State != target.Disabled {
t.Fatalf("incompatible result = %#v events=%v", result, record.events)
}
})
}
}
func TestDockerDaemonNeverEscalatesAndReportsPermissionNormally(t *testing.T) {
a, record, _, _, runner := fixture(t)
runner.fail["systemctl daemon-reload"] = os.ErrPermission
got := a.SetDesired(context.Background(), true, activeProfile())
if got.Err == nil || !errors.Is(got.Err, os.ErrPermission) || got.Inspection.State != target.Enabled {
t.Fatalf("permission = %#v", got)
}
for _, event := range record.events {
if strings.Contains(event, "sudo") || strings.Contains(event, "pkexec") {
t.Fatalf("privilege escalation: %v", record.events)
}
}
}
func TestDockerDaemonInspectionHasAllFourStates(t *testing.T) {
a, _, daemonPath, _, _ := fixture(t)
ctx := context.Background()
if got := a.Inspect(ctx, activeProfile()).State; got != target.Disabled {
t.Fatalf("disabled = %s", got)
}
if err := os.MkdirAll(filepath.Dir(daemonPath), 0o700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(daemonPath, []byte(`{"proxies":{"http-proxy":"http://external.example:8080"}}`), 0o600); err != nil {
t.Fatal(err)
}
if got := a.Inspect(ctx, activeProfile()).State; got != target.External {
t.Fatalf("external = %s", got)
}
if got := a.SetDesired(ctx, true, activeProfile()); got.Err != nil {
t.Fatal(got.Err)
}
if got := a.Inspect(ctx, activeProfile()).State; got != target.Enabled {
t.Fatalf("enabled = %s", got)
}
if err := os.WriteFile(daemonPath, []byte(`{"proxies":{"http-proxy":"http://changed.example:8080"}}`), 0o600); err != nil {
t.Fatal(err)
}
if got := a.Inspect(ctx, activeProfile()).State; got != target.Drifted {
t.Fatalf("drifted = %s", got)
}
}
func assertSubsequence(t *testing.T, got, want []string) {
t.Helper()
index := 0
for _, event := range got {
if index < len(want) && event == want[index] {
index++
}
}
if index != len(want) {
t.Fatalf("events %v do not contain ordered %v", got, want)
}
}
func mutationEvents(events []string) []string {
var result []string
for _, event := range events {
if strings.HasPrefix(event, "write ") || strings.HasPrefix(event, "remove ") || strings.Contains(event, "daemon-reload") || strings.Contains(event, " restart ") {
result = append(result, event)
}
}
return result
}
func contains(values []string, want string) bool {
for _, value := range values {
if value == want {
return true
}
}
return false
}