From be355057dce7b588720019247985f0a37b176717 Mon Sep 17 00:00:00 2001 From: Nayden Petrov Date: Thu, 29 Oct 2026 07:53:26 +0200 Subject: [PATCH 1/4] Add env.bosh.systemd_resolved.cache operator setting Adds env.bosh.systemd_resolved.cache (yes|no|no-negative) to the agent settings. The agent writes Cache= into /etc/systemd/resolved.conf.d/10-bosh.conf at boot, defaulting to yes. On stemcells without systemd-resolved the setting is a no-op. The implementation follows the SetupIPv6 pattern: a dedicated SetupSystemdResolved method at each layer (Platform, Manager, DNSResolver), called from bootstrap.go before SetupNetworking. --- agent/bootstrap.go | 4 ++ platform/dummy_platform.go | 4 ++ platform/linux_platform.go | 4 ++ .../dnsresolver/dns_resolveconf_resolver.go | 4 ++ platform/net/dnsresolver/dns_resolver.go | 7 +- .../net/dnsresolver/dns_systemd_resolver.go | 48 ++++++++----- .../dnsresolver/dns_systemd_resolver_test.go | 42 +++++++++-- .../dnsresolver/fakes/fake_dns_resolver.go | 9 +++ platform/net/fakes/fake_net_manager.go | 8 +++ platform/net/net_manager_interface.go | 1 + platform/net/ubuntu_net_manager.go | 5 ++ platform/net/windows_net_manager.go | 4 ++ platform/platform_interface.go | 1 + platform/platformfakes/fake_platform.go | 72 +++++++++++++++++++ platform/windows_platform.go | 4 ++ settings/settings.go | 38 ++++++---- settings/settings_test.go | 12 ++++ 17 files changed, 228 insertions(+), 39 deletions(-) diff --git a/agent/bootstrap.go b/agent/bootstrap.go index b2a31984f..310099af9 100644 --- a/agent/bootstrap.go +++ b/agent/bootstrap.go @@ -96,6 +96,10 @@ func (boot bootstrap) Run() (err error) { //nolint:gocyclo return bosherr.WrapError(err, "Setting up hostname") } + if err = boot.platform.SetupSystemdResolved(settings.Env.Bosh.SystemdResolved); err != nil { + return bosherr.WrapError(err, "Setting up systemd-resolved") + } + if err = boot.platform.SetupNetworking(settings.Networks, settings.GetMbusURLs()); err != nil { return bosherr.WrapError(err, "Setting up networking") } diff --git a/platform/dummy_platform.go b/platform/dummy_platform.go index ca30731d4..40e8abf7e 100644 --- a/platform/dummy_platform.go +++ b/platform/dummy_platform.go @@ -196,6 +196,10 @@ func (p dummyPlatform) SetupHostname(hostname string) (err error) { return } +func (p dummyPlatform) SetupSystemdResolved(_ boshsettings.SystemdResolvedSettings) error { + return nil +} + func (p dummyPlatform) SetupNetworking(networks boshsettings.Networks, mbus []string) (err error) { return } diff --git a/platform/linux_platform.go b/platform/linux_platform.go index 0e9f25e39..43c9f6d49 100644 --- a/platform/linux_platform.go +++ b/platform/linux_platform.go @@ -263,6 +263,10 @@ func (p linux) GetAuditLogger() AuditLogger { return p.auditLogger } +func (p linux) SetupSystemdResolved(resolved boshsettings.SystemdResolvedSettings) error { + return p.netManager.SetupSystemdResolved(resolved) +} + func (p linux) SetupNetworking(networks boshsettings.Networks, mbus []string) (err error) { return p.netManager.SetupNetworking(networks, mbus, nil) } diff --git a/platform/net/dnsresolver/dns_resolveconf_resolver.go b/platform/net/dnsresolver/dns_resolveconf_resolver.go index de7a6b719..ba7044b64 100644 --- a/platform/net/dnsresolver/dns_resolveconf_resolver.go +++ b/platform/net/dnsresolver/dns_resolveconf_resolver.go @@ -9,6 +9,8 @@ import ( bosherr "github.com/cloudfoundry/bosh-utils/errors" boshsys "github.com/cloudfoundry/bosh-utils/system" + + boshsettings "github.com/cloudfoundry/bosh-agent/v2/settings" ) const ( @@ -59,6 +61,8 @@ func (d *resolveConfResolver) Validate(dnsServers []string) error { return bosherr.WrapError(err, "None of the DNS servers that were specified in the manifest were found in /etc/resolv.conf.") } +func (d *resolveConfResolver) SetupSystemdResolved(_ boshsettings.SystemdResolvedSettings) {} + func (d *resolveConfResolver) SetupDNS(dnsServers []string) error { buffer := bytes.NewBuffer([]byte{}) diff --git a/platform/net/dnsresolver/dns_resolver.go b/platform/net/dnsresolver/dns_resolver.go index 2543c71d3..fe6a1e344 100644 --- a/platform/net/dnsresolver/dns_resolver.go +++ b/platform/net/dnsresolver/dns_resolver.go @@ -1,6 +1,11 @@ package dnsresolver +import ( + boshsettings "github.com/cloudfoundry/bosh-agent/v2/settings" +) + type DNSResolver interface { Validate([]string) error - SetupDNS([]string) error + SetupDNS(dnsServers []string) error + SetupSystemdResolved(boshsettings.SystemdResolvedSettings) } diff --git a/platform/net/dnsresolver/dns_systemd_resolver.go b/platform/net/dnsresolver/dns_systemd_resolver.go index 7c5ed3618..4dc74f73a 100644 --- a/platform/net/dnsresolver/dns_systemd_resolver.go +++ b/platform/net/dnsresolver/dns_systemd_resolver.go @@ -2,24 +2,31 @@ package dnsresolver import ( "bytes" - "html/template" + "fmt" gonet "net" "strings" bosherr "github.com/cloudfoundry/bosh-utils/errors" boshsys "github.com/cloudfoundry/bosh-utils/system" -) -const ( - systemdResolvedTemplate = `# Generated by bosh-agent -[Resolve] -DNS={{ range . }}{{ . }}{{ " " -}} -{{ end }}` + boshsettings "github.com/cloudfoundry/bosh-agent/v2/settings" ) +// defaultCacheMode preserves the behaviour baked into the stemcell +// (bosh-linux-stemcell-builder enables negative caching) when the operator +// does not set env.bosh.systemd_resolved.cache. +const defaultCacheMode = "yes" + +var validCacheModes = map[string]struct{}{ + "yes": {}, + "no": {}, + "no-negative": {}, +} + type systemdResolver struct { fs boshsys.FileSystem cmdRunner boshsys.CmdRunner + resolved boshsettings.SystemdResolvedSettings } func NewSystemdResolver( @@ -58,24 +65,29 @@ func (d *systemdResolver) Validate(dnsServers []string) error { return bosherr.WrapError(err, "None of the DNS servers that were specified in the manifest were found in /etc/systemd/resolved.conf.d/10-bosh.conf.") } +func (d *systemdResolver) SetupSystemdResolved(resolved boshsettings.SystemdResolvedSettings) { + d.resolved = resolved +} + func (d *systemdResolver) SetupDNS(dnsServers []string) error { - // mkdir /etc/systemd/resolved.conf.d/ - // create /etc/systemd/resolved.conf.d/10-bosh.conf - // contents (if your dns Servers are 1.1.1.1 and 8.8.8.8): - // DNS=8.8.8.8 1.1.1.1 - if len(dnsServers) == 0 { - return nil + cacheMode := d.resolved.Cache + if cacheMode == "" { + cacheMode = defaultCacheMode + } + if _, ok := validCacheModes[cacheMode]; !ok { + return bosherr.Errorf("Invalid systemd-resolved cache mode %q (expected yes, no, or no-negative)", cacheMode) } + var buffer bytes.Buffer - t := template.Must(template.New("resolv-conf").Parse(systemdResolvedTemplate)) - err := t.Execute(&buffer, dnsServers) - if err != nil { - return bosherr.WrapError(err, "Generating DNS config from template") + buffer.WriteString("# Generated by bosh-agent\n[Resolve]\n") + if len(dnsServers) > 0 { + fmt.Fprintf(&buffer, "DNS=%s\n", strings.Join(dnsServers, " ")) } + fmt.Fprintf(&buffer, "Cache=%s\n", cacheMode) // Create the directory; it doesn't exist on a vanilla Noble install dirPath := "/etc/systemd/resolved.conf.d/" - err = d.fs.MkdirAll(dirPath, 0755) + err := d.fs.MkdirAll(dirPath, 0755) if err != nil { return bosherr.WrapError(err, "Creating directory "+dirPath) } diff --git a/platform/net/dnsresolver/dns_systemd_resolver_test.go b/platform/net/dnsresolver/dns_systemd_resolver_test.go index ddd7c317b..88b5af89f 100644 --- a/platform/net/dnsresolver/dns_systemd_resolver_test.go +++ b/platform/net/dnsresolver/dns_systemd_resolver_test.go @@ -6,6 +6,7 @@ import ( . "github.com/onsi/gomega" . "github.com/cloudfoundry/bosh-agent/v2/platform/net/dnsresolver" + boshsettings "github.com/cloudfoundry/bosh-agent/v2/settings" ) var _ = Describe("systemdConfResolver", func() { @@ -57,26 +58,55 @@ var _ = Describe("systemdConfResolver", func() { }) Context("when there are configured DNS servers", func() { - It("writes DNS to /etc/systemd/resolved.conf.d/10-bosh.conf", func() { + It("writes DNS and the default cache mode to /etc/systemd/resolved.conf.d/10-bosh.conf", func() { err := systemdResolver.SetupDNS([]string{"8.8.8.8", "9.9.9.9"}) Expect(err).ToNot(HaveOccurred()) resolvConfBase := fs.GetFileTestStat("/etc/systemd/resolved.conf.d/10-bosh.conf") Expect(resolvConfBase).ToNot(BeNil()) - - expectedResolvConfBase := `# Generated by bosh-agent + Expect(resolvConfBase.StringContents()).To(Equal(`# Generated by bosh-agent [Resolve] -DNS=8.8.8.8 9.9.9.9` - Expect(resolvConfBase.StringContents()).To(ContainSubstring(expectedResolvConfBase)) +DNS=8.8.8.8 9.9.9.9 +Cache=yes +`)) Expect(cmdRunner.RunCommands[0]).To(Equal([]string{"systemctl", "restart", "systemd-resolved"})) }) + + It("honors the operator-provided cache mode", func() { + systemdResolver.SetupSystemdResolved(boshsettings.SystemdResolvedSettings{Cache: "no-negative"}) + err := systemdResolver.SetupDNS([]string{"8.8.8.8"}) + Expect(err).ToNot(HaveOccurred()) + + resolvConfBase := fs.GetFileTestStat("/etc/systemd/resolved.conf.d/10-bosh.conf") + Expect(resolvConfBase).ToNot(BeNil()) + Expect(resolvConfBase.StringContents()).To(Equal(`# Generated by bosh-agent +[Resolve] +DNS=8.8.8.8 +Cache=no-negative +`)) + }) }) Context("when no DNS servers are configured", func() { - It("return nil", func() { + It("still writes the cache mode and returns nil", func() { err := systemdResolver.SetupDNS([]string{}) Expect(err).NotTo(HaveOccurred()) + + resolvConfBase := fs.GetFileTestStat("/etc/systemd/resolved.conf.d/10-bosh.conf") + Expect(resolvConfBase).ToNot(BeNil()) + Expect(resolvConfBase.StringContents()).To(Equal(`# Generated by bosh-agent +[Resolve] +Cache=yes +`)) }) }) + Context("when the cache mode is invalid", func() { + It("returns an error", func() { + systemdResolver.SetupSystemdResolved(boshsettings.SystemdResolvedSettings{Cache: "bogus"}) + err := systemdResolver.SetupDNS([]string{"8.8.8.8"}) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Invalid systemd-resolved cache mode")) + }) + }) }) diff --git a/platform/net/dnsresolver/fakes/fake_dns_resolver.go b/platform/net/dnsresolver/fakes/fake_dns_resolver.go index d35a92b76..4f4440aeb 100644 --- a/platform/net/dnsresolver/fakes/fake_dns_resolver.go +++ b/platform/net/dnsresolver/fakes/fake_dns_resolver.go @@ -1,6 +1,11 @@ package fakes +import ( + boshsettings "github.com/cloudfoundry/bosh-agent/v2/settings" +) + type FakeDNSResolver struct { + SetupSystemdResolvedConfig boshsettings.SystemdResolvedSettings } func (f *FakeDNSResolver) Validate(dnsServers []string) error { @@ -10,3 +15,7 @@ func (f *FakeDNSResolver) Validate(dnsServers []string) error { func (f *FakeDNSResolver) SetupDNS(dnsServers []string) error { return nil } + +func (f *FakeDNSResolver) SetupSystemdResolved(resolved boshsettings.SystemdResolvedSettings) { + f.SetupSystemdResolvedConfig = resolved +} diff --git a/platform/net/fakes/fake_net_manager.go b/platform/net/fakes/fake_net_manager.go index cf8132723..6a15c4448 100644 --- a/platform/net/fakes/fake_net_manager.go +++ b/platform/net/fakes/fake_net_manager.go @@ -10,6 +10,9 @@ type FakeManager struct { SetupNetworkingNetworks boshsettings.Networks SetupNetworkingErr error + SetupSystemdResolvedConfig boshsettings.SystemdResolvedSettings + SetupSystemdResolvedErr error + SetupIPv6Config boshsettings.IPv6 SetupIPv6StopCh <-chan struct{} SetupIPv6Err error @@ -27,6 +30,11 @@ func (net *FakeManager) SetupIPv6(config boshsettings.IPv6, stopCh <-chan struct return net.SetupIPv6Err } +func (net *FakeManager) SetupSystemdResolved(resolved boshsettings.SystemdResolvedSettings) error { + net.SetupSystemdResolvedConfig = resolved + return net.SetupSystemdResolvedErr +} + func (net *FakeManager) SetupNetworking(networks boshsettings.Networks, mbus []string, errCh chan error) error { net.SetupNetworkingNetworks = networks return net.SetupNetworkingErr diff --git a/platform/net/net_manager_interface.go b/platform/net/net_manager_interface.go index 0339a9d28..d4376bc18 100644 --- a/platform/net/net_manager_interface.go +++ b/platform/net/net_manager_interface.go @@ -14,4 +14,5 @@ type Manager interface { GetConfiguredNetworkInterfaces() ([]string, error) SetupIPv6(boshsettings.IPv6, <-chan struct{}) error + SetupSystemdResolved(boshsettings.SystemdResolvedSettings) error } diff --git a/platform/net/ubuntu_net_manager.go b/platform/net/ubuntu_net_manager.go index 325af24c4..f9c12a6a4 100644 --- a/platform/net/ubuntu_net_manager.go +++ b/platform/net/ubuntu_net_manager.go @@ -108,6 +108,11 @@ func (net UbuntuNetManager) SetupIPv6(config boshsettings.IPv6, stopCh <-chan st return nil } +func (net UbuntuNetManager) SetupSystemdResolved(resolved boshsettings.SystemdResolvedSettings) error { + net.dnsResolver.SetupSystemdResolved(resolved) + return nil +} + func (net UbuntuNetManager) SetupNetworking(networks boshsettings.Networks, mbus []string, errCh chan error) error { if networks.IsPreconfigured() { // Note in this case IPs are not broadcast diff --git a/platform/net/windows_net_manager.go b/platform/net/windows_net_manager.go index 381c3343f..a68b42ebe 100644 --- a/platform/net/windows_net_manager.go +++ b/platform/net/windows_net_manager.go @@ -106,6 +106,10 @@ func (net WindowsNetManager) setupNetworkInterfaces(networks boshsettings.Networ return nil } +func (net WindowsNetManager) SetupSystemdResolved(_ boshsettings.SystemdResolvedSettings) error { + return nil +} + func (net WindowsNetManager) SetupNetworking(networks boshsettings.Networks, mbus []string, errCh chan error) error { if err := net.setupNetworkInterfaces(networks); err != nil { return bosherr.WrapError(err, "setting up network interfaces") diff --git a/platform/platform_interface.go b/platform/platform_interface.go index 2055f3dea..231c0ba75 100644 --- a/platform/platform_interface.go +++ b/platform/platform_interface.go @@ -60,6 +60,7 @@ type Platform interface { SetupBoshSettingsDisk() (err error) SetupIPv6(boshsettings.IPv6) error SetupHostname(hostname string) (err error) + SetupSystemdResolved(boshsettings.SystemdResolvedSettings) error SetupNetworking(networks boshsettings.Networks, mbus []string) (err error) SetupLogrotate(groupName, basePath, size string) (err error) SetTimeWithNtpServers(servers []string) (err error) diff --git a/platform/platformfakes/fake_platform.go b/platform/platformfakes/fake_platform.go index 1cf9690f5..36c4b872f 100644 --- a/platform/platformfakes/fake_platform.go +++ b/platform/platformfakes/fake_platform.go @@ -595,6 +595,17 @@ type FakePlatform struct { setupIPv6ReturnsOnCall map[int]struct { result1 error } + SetupSystemdResolvedStub func(settings.SystemdResolvedSettings) error + setupSystemdResolvedMutex sync.RWMutex + setupSystemdResolvedArgsForCall []struct { + arg1 settings.SystemdResolvedSettings + } + setupSystemdResolvedReturns struct { + result1 error + } + setupSystemdResolvedReturnsOnCall map[int]struct { + result1 error + } SetupLogDirStub func() error setupLogDirMutex sync.RWMutex setupLogDirArgsForCall []struct { @@ -3778,6 +3789,67 @@ func (fake *FakePlatform) SetupIPv6ReturnsOnCall(i int, result1 error) { }{result1} } +func (fake *FakePlatform) SetupSystemdResolved(arg1 settings.SystemdResolvedSettings) error { + fake.setupSystemdResolvedMutex.Lock() + ret, specificReturn := fake.setupSystemdResolvedReturnsOnCall[len(fake.setupSystemdResolvedArgsForCall)] + fake.setupSystemdResolvedArgsForCall = append(fake.setupSystemdResolvedArgsForCall, struct { + arg1 settings.SystemdResolvedSettings + }{arg1}) + stub := fake.SetupSystemdResolvedStub + fakeReturns := fake.setupSystemdResolvedReturns + fake.recordInvocation("SetupSystemdResolved", []interface{}{arg1}) + fake.setupSystemdResolvedMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakePlatform) SetupSystemdResolvedCallCount() int { + fake.setupSystemdResolvedMutex.RLock() + defer fake.setupSystemdResolvedMutex.RUnlock() + return len(fake.setupSystemdResolvedArgsForCall) +} + +func (fake *FakePlatform) SetupSystemdResolvedCalls(stub func(settings.SystemdResolvedSettings) error) { + fake.setupSystemdResolvedMutex.Lock() + defer fake.setupSystemdResolvedMutex.Unlock() + fake.SetupSystemdResolvedStub = stub +} + +func (fake *FakePlatform) SetupSystemdResolvedArgsForCall(i int) settings.SystemdResolvedSettings { + fake.setupSystemdResolvedMutex.RLock() + defer fake.setupSystemdResolvedMutex.RUnlock() + argsForCall := fake.setupSystemdResolvedArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakePlatform) SetupSystemdResolvedReturns(result1 error) { + fake.setupSystemdResolvedMutex.Lock() + defer fake.setupSystemdResolvedMutex.Unlock() + fake.SetupSystemdResolvedStub = nil + fake.setupSystemdResolvedReturns = struct { + result1 error + }{result1} +} + +func (fake *FakePlatform) SetupSystemdResolvedReturnsOnCall(i int, result1 error) { + fake.setupSystemdResolvedMutex.Lock() + defer fake.setupSystemdResolvedMutex.Unlock() + fake.SetupSystemdResolvedStub = nil + if fake.setupSystemdResolvedReturnsOnCall == nil { + fake.setupSystemdResolvedReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.setupSystemdResolvedReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakePlatform) SetupLogDir() error { fake.setupLogDirMutex.Lock() ret, specificReturn := fake.setupLogDirReturnsOnCall[len(fake.setupLogDirArgsForCall)] diff --git a/platform/windows_platform.go b/platform/windows_platform.go index 63e835433..aa78dd15d 100644 --- a/platform/windows_platform.go +++ b/platform/windows_platform.go @@ -404,6 +404,10 @@ func (p WindowsPlatform) SetupHostname(hostname string) (err error) { return } +func (p WindowsPlatform) SetupSystemdResolved(_ boshsettings.SystemdResolvedSettings) error { + return nil +} + func (p WindowsPlatform) SetupNetworking(networks boshsettings.Networks, mbus []string) (err error) { return p.netManager.SetupNetworking(networks, mbus, nil) } diff --git a/settings/settings.go b/settings/settings.go index 9c66e0961..46af169bd 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -294,20 +294,21 @@ func (e Env) GetParallel() *int { } type BoshEnv struct { - Agent AgentEnv `json:"agent"` - Password string `json:"password"` - KeepRootPassword bool `json:"keep_root_password"` - RemoveDevTools bool `json:"remove_dev_tools"` - RemoveStaticLibraries bool `json:"remove_static_libraries"` - AuthorizedKeys []string `json:"authorized_keys"` - SwapSizeInMB *uint64 `json:"swap_size"` - Mbus MBus `json:"mbus"` - IPv6 IPv6 `json:"ipv6"` - JobDir JobDir `json:"job_dir"` - RunDir RunDir `json:"run_dir"` - Blobstores []Blobstore `json:"blobstores"` - NTP []string `json:"ntp,omitempty"` - Parallel *int `json:"parallel"` + Agent AgentEnv `json:"agent"` + Password string `json:"password"` + KeepRootPassword bool `json:"keep_root_password"` + RemoveDevTools bool `json:"remove_dev_tools"` + RemoveStaticLibraries bool `json:"remove_static_libraries"` + AuthorizedKeys []string `json:"authorized_keys"` + SwapSizeInMB *uint64 `json:"swap_size"` + Mbus MBus `json:"mbus"` + IPv6 IPv6 `json:"ipv6"` + JobDir JobDir `json:"job_dir"` + RunDir RunDir `json:"run_dir"` + Blobstores []Blobstore `json:"blobstores"` + NTP []string `json:"ntp,omitempty"` + Parallel *int `json:"parallel"` + SystemdResolved SystemdResolvedSettings `json:"systemd_resolved"` } type AgentEnv struct { @@ -345,6 +346,15 @@ type RunDir struct { TmpFSSize string `json:"tmpfs_size"` } +// SystemdResolvedSettings configures systemd-resolved directives the agent +// renders into its resolved.conf.d drop-in. Honored only on stemcells where +// systemd-resolved is the resolver; ignored elsewhere. +type SystemdResolvedSettings struct { + // Cache maps to resolved.conf Cache=; one of "yes", "no", "no-negative". + // Empty means leave the agent default ("yes"). + Cache string `json:"cache"` +} + type DNSRecords struct { Version uint64 `json:"Version"` Records [][2]string `json:"records"` diff --git a/settings/settings_test.go b/settings/settings_test.go index 86dc2d969..c96dda798 100644 --- a/settings/settings_test.go +++ b/settings/settings_test.go @@ -974,6 +974,18 @@ var _ = Describe("Settings", func() { Expect(env.Bosh.RunDir).To(Equal(RunDir{TmpFSSize: "37m"})) }) + It("can set the systemd-resolved cache mode", func() { + env := Env{} + err := json.Unmarshal([]byte(`{"bosh": {} }`), &env) + Expect(err).NotTo(HaveOccurred()) + Expect(env.Bosh.SystemdResolved).To(Equal(SystemdResolvedSettings{})) + + env = Env{} + err = json.Unmarshal([]byte(`{"bosh": {"systemd_resolved": {"cache": "no-negative"} } }`), &env) + Expect(err).NotTo(HaveOccurred()) + Expect(env.Bosh.SystemdResolved).To(Equal(SystemdResolvedSettings{Cache: "no-negative"})) + }) + Context("when swap_size is not specified in the json", func() { It("unmarshalls correctly", func() { var env Env From 1938e467381c7da36dccca605f14d09bfd6b53a6 Mon Sep 17 00:00:00 2001 From: Nayden Petrov Date: Fri, 30 Oct 2026 08:54:04 +0200 Subject: [PATCH 2/4] Add bootstrap tests for systemd-resolved wiring Tests that the cache mode is forwarded to SetupSystemdResolved before SetupNetworking is called, and that an error from SetupSystemdResolved stops bootstrap with the wrapped context. --- agent/bootstrap_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/agent/bootstrap_test.go b/agent/bootstrap_test.go index f778915cb..e091999c5 100644 --- a/agent/bootstrap_test.go +++ b/agent/bootstrap_test.go @@ -229,6 +229,46 @@ var _ = Describe("bootstrap", func() { Expect(platform.SetupHostnameArgsForCall(0)).To(Equal("foo-bar-baz-123")) }) + It("forwards the systemd-resolved settings to the platform", func() { + settingsService.Settings.Env.Bosh.SystemdResolved = boshsettings.SystemdResolvedSettings{Cache: "no-negative"} + + err := bootstrap() + Expect(err).NotTo(HaveOccurred()) + Expect(platform.SetupSystemdResolvedCallCount()).To(Equal(1)) + Expect(platform.SetupSystemdResolvedArgsForCall(0)).To(Equal(boshsettings.SystemdResolvedSettings{Cache: "no-negative"})) + }) + + It("calls SetupSystemdResolved before SetupNetworking", func() { + var resolvedOrder, networkingOrder int + callIndex := 0 + platform.SetupSystemdResolvedStub = func(_ boshsettings.SystemdResolvedSettings) error { + callIndex++ + resolvedOrder = callIndex + return nil + } + platform.SetupNetworkingStub = func(_ boshsettings.Networks, _ []string) error { + callIndex++ + networkingOrder = callIndex + return nil + } + + err := bootstrap() + Expect(err).NotTo(HaveOccurred()) + Expect(resolvedOrder).To(BeNumerically("<", networkingOrder)) + }) + + Context("when setting up systemd-resolved fails", func() { + BeforeEach(func() { + platform.SetupSystemdResolvedReturns(errors.New("fake-systemd-resolved-err")) + }) + + It("returns an error", func() { + err := bootstrap() + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("fake-systemd-resolved-err")) + }) + }) + It("fetches initial settings", func() { err := bootstrap() Expect(err).NotTo(HaveOccurred()) From 1ea0f82c47ad8672e10fe7620cdcaf855b6a8c87 Mon Sep 17 00:00:00 2001 From: Nayden Petrov Date: Fri, 30 Oct 2026 09:05:43 +0200 Subject: [PATCH 3/4] Assert networking is skipped when systemd-resolved setup fails --- agent/bootstrap_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/bootstrap_test.go b/agent/bootstrap_test.go index e091999c5..3d1c3a29a 100644 --- a/agent/bootstrap_test.go +++ b/agent/bootstrap_test.go @@ -262,10 +262,11 @@ var _ = Describe("bootstrap", func() { platform.SetupSystemdResolvedReturns(errors.New("fake-systemd-resolved-err")) }) - It("returns an error", func() { + It("returns an error and does not attempt networking", func() { err := bootstrap() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-systemd-resolved-err")) + Expect(platform.SetupNetworkingCallCount()).To(Equal(0)) }) }) From 7545d082ba6335249c37c6d637d697aa2c8f09c6 Mon Sep 17 00:00:00 2001 From: Nayden Petrov Date: Fri, 30 Oct 2026 09:06:33 +0200 Subject: [PATCH 4/4] Revert redundant SetupNetworkingCallCount assertion --- agent/bootstrap_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/agent/bootstrap_test.go b/agent/bootstrap_test.go index 3d1c3a29a..e091999c5 100644 --- a/agent/bootstrap_test.go +++ b/agent/bootstrap_test.go @@ -262,11 +262,10 @@ var _ = Describe("bootstrap", func() { platform.SetupSystemdResolvedReturns(errors.New("fake-systemd-resolved-err")) }) - It("returns an error and does not attempt networking", func() { + It("returns an error", func() { err := bootstrap() Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("fake-systemd-resolved-err")) - Expect(platform.SetupNetworkingCallCount()).To(Equal(0)) }) })